Skip to content

chore: ship the licence text and require a CLA - #91

Merged
polaz merged 12 commits into
mainfrom
chore/#90-licence-cla
Sep 5, 2026
Merged

chore: ship the licence text and require a CLA#91
polaz merged 12 commits into
mainfrom
chore/#90-licence-cla

Conversation

@polaz

@polaz polaz commented Sep 5, 2026

Copy link
Copy Markdown
Member

Summary

  • Every package directory now carries the Apache-2.0 text and a NOTICE naming the copyright holder, so the built wheels include both (dist-info/licenses/LICENSE, dist-info/licenses/NOTICE).
  • Contributions are accepted under a Contributor License Agreement (CLA.md); a CLA Assistant workflow collects signatures in the pull request and stores them on the cla-signatures branch.
  • Package metadata names the maintainer; the README's support section keeps only the channel that exists.

Testing

uv build --wheel in coordinode/ produces a wheel whose dist-info/licenses/ contains LICENSE (11,358 bytes, canonical text) and NOTICE. The workflow file parses as YAML; its first real run happens on the next external pull request.

Closes #90

polaz added 11 commits September 1, 2026 23:54
The server's advisor groups what it measures by the shape of the query, so
its report can name a statement but not the place that wrote it: one line of
Cypher usually has a dozen call sites. Built with debug_source_tracking, the
client now sends the file, line and function each query was written at, and
the report names the line instead. app_name and app_version ride along,
naming the service when several share a database.

The keys are the contract the Rust driver already speaks, and this fills the
one it has to leave empty: it reads the caller through #[track_caller], and a
Location carries no function name, while a Python frame does.

Off by default and free while off: the flag gates the frame read itself, and
the metadata argument is not passed at all, so a query makes the call it made
before this existed.

The frame is read directly rather than through inspect.stack, which walks the
whole stack and opens each frame's source file to quote lines around it: file
I/O per frame to answer a question about one of them.

The synchronous client reads the location on the way in rather than inside
the coroutine, because it hands that coroutine to an event loop and the frame
that called it has returned by the time it runs. A query handed to
create_task is past saving that way, so it reports nothing: what is left on
the stack is an event-loop frame, and the advisor would collect the queries
of every unrelated task under that one line, which is worse than silence.
Reading the location inside the coroutine only worked for a query that was
awaited where it was written. Everything that runs a coroutine as a task
starts the body long after the calling frame has returned, so create_task,
gather, wait_for, shield and TaskGroup all reported an event-loop frame,
which the call-site filter then discarded: concurrent queries, the ones most
worth attributing, silently sent nothing. The query methods now read the
location when they are CALLED, which is the one moment all of those share,
and are plain methods returning a coroutine so there is such a moment at all.

Two ways the aid could take a query down with it, both now closed.

A path or function name outside ASCII went into a metadata key with no -bin
suffix, which carries an HTTP/2 header value and must be ASCII; gRPC enforces
that on the client, so the call failed before it was sent and the query never
reached the server. A checkout under a non-ASCII path is enough to trigger
it, and Python allows such a function name too. The values are escaped now,
not dropped: an escaped path still names the file and still groups with
itself, and dropping the file key alone would make the server discard the
whole context anyway.

The frame read caught only ValueError, the failure a stack shorter than the
walk gives. An application whose audit hook refuses the sys._getframe event
raises whatever it likes instead, and that escaped before the request was
built. To a caller all of these are one thing — no location — and none is
worth failing a query over.

Carries a regression test per fix: the five scheduling wrappers, a location
whose file, function and application name are all non-ASCII, and a frame read
that raises. All were seen failing first.
The previous escape covered the wrong range. gRPC refuses a control character
in a metadata value exactly as it refuses a non-ASCII one, and 0x7f with them,
but backslashreplace leaves all of those untouched: a newline, a tab or a NUL
went out raw and failed the call before it was sent. So enabling source
tracking could still be the reason every query fails, which is what the escape
existed to prevent.

The likeliest way in is mundane rather than exotic. An application name read
from a file arrives with the newline that ended it, and a POSIX path may
legally contain one.

The bar is now printable ASCII, stated as a predicate rather than delegated to
a codec whose range would have to be verified. Values that need nothing — every
ordinary path and name — are returned unchanged after two scans, so the common
path allocates nothing where it previously built a new string every time.

Carries a regression test with a newline, a tab and a NUL across the file,
function and application values, seen failing first. The escaped output was
also put through grpc to confirm it now reaches the transport, where the raw
one was rejected.
…g call sites

Two ways the previous round went wrong.

Making the query methods plain methods returning a coroutine took the call
site back from every scheduling wrapper, but it also changed the answer to a
question callers ask: iscoroutinefunction said False. Code that dispatches on
it broke, most consequentially create_autospec, which then built a
synchronous double whose result a test cannot await. That happened with
tracking off, so it fell on people not using the feature at all. The methods
are now marked as standing in for the coroutine functions they replace, both
ways, since the lever differs by version.

The escaping was not injective, and for this feature that is not a lost
detail but a wrong answer: the advisor groups by what it receives, so two
call sites arriving as one string are reported as one place in the code, with
the queries of one attributed to the other. A name holding a real newline
came out as a name holding the four characters that spell its escape, and a
character outside the basic plane came out as a character inside it followed
by a digit. The escape character now escapes itself and each form is padded
to a fixed width.

Carries a regression test per defect, each seen failing first: both
introspection predicates and an autospecced double that gets awaited, a
newline against its literal spelling, and an astral character against a BMP
one followed by a digit. The encoding was also brute-forced over an alphabet
of the characters that trip it, three deep: no collisions, nothing outside
the permitted range.
…ines

Marking a plain method as standing in for a coroutine function only worked
from 3.12, where the marker is honoured. On 3.11 it reaches
asyncio.iscoroutinefunction and therefore mock, but inspect.iscoroutinefunction
has no such lever and answered False, which the tests caught there.

The methods are coroutine functions again, and the location is read at the
moment that serves both halves: attribute lookup. It happens in the caller's
own frame, and it happens for every way of running the query, since
create_task, gather, wait_for, shield and a TaskGroup all begin with
`client.cypher(...)`. What the caller gets is a partial over the coroutine
function, which inspect looks through, so the predicates are right on every
supported version rather than on the newest ones.

The signature is declared without self, because a descriptor that is not a
plain function is not recognised as a method by create_autospec, which then
leaves self in and binds the first real argument to it.

With tracking off the lookup returns the ordinary bound method: no partial,
no frame read, nothing.

Verified on 3.11 as well as 3.12, since the version was the whole point:
both predicates on the class, on a transaction and on a tracking instance,
and an autospecced double that gets awaited.
Rewriting the underlying function's signature to drop `self` was read
again every time Python bound the method, taking the first remaining
parameter off with it: `inspect.signature(client.cypher)` reported
`params` first and no `query` at all, with tracking off as well as on.
Anything inspecting a bound callable to validate arguments, inject
dependencies or generate a wrapper would build that interface.

The self-less signature exists for `create_autospec`, which reaches the
method through the class, so it now lives on a separate object returned
by class-level access only. The function every binding is derived from
keeps its true signature.

Carries a regression test on both bindings and on a transaction
statement.

Part of #78
Reading the call site when the method was looked up filed every query
of a bound method kept for later under the one line that stored it:
`run_query = client.cypher` followed by calls from a dozen places
reported the assignment. Dependency injection and callback-style code
hold on to a bound method routinely, so the advisor would merge exactly
the call sites the feature exists to tell apart.

The read moves into the call, which is the moment every path shares:
`client.cypher(...)` evaluates in the caller's own frame whatever is
then done with the coroutine, so scheduling it as a task still reports
the caller and not the event loop. The coroutine contract holds because
the wrapper derives from functools.partial, which the predicates unwrap.

Carries a regression test for a saved method, and for two of its calls
staying two distinct locations.

Part of #78
create_text_index and drop_text_index reach the server through an
internal cypher call, so the location read there was a frame inside
this package: not a call site, discarded, and two public query paths
left silently outside the per-query attribution the client advertises.

Both helpers now read their own caller and pass it down to the
statement, on the asynchronous and the synchronous client alike.

Carries a regression test per helper on each client, and one holding
the default path quiet.

Part of #78
Reading `f_code` raises its own `object.__getattr__` audit event, apart
from the `sys._getframe` one, so a hardened application permitting the
first and rejecting the second had the exception come out of the read —
past the guard, and into a query that then failed rather than going out
unattributed. A debugging aid must never be why a query fails, whichever
of the two events the hook objects to.

The extraction moves inside the guard that already covers getting the
frame, which is the same nothing to a caller either way.

Carries a regression test per refused attribute, and one for the query
that still goes out.

Part of #78
The unbound form — passing the instance in, as
AsyncCoordinodeClient.cypher(client, "...") — reaches the method through
the class and so bypasses the binding entirely. Nothing on the way in
read the caller, and a client with tracking on sent that query with no
location at all: quiet, since an unattributed query is also what every
unreadable frame produces.

It now reads its caller as well. From inside the coroutine, which is as
early as this form allows and right for the direct await it is written
as; scheduling this particular form as a task lands on an event-loop
frame and is left unattributed rather than misattributed, as every
unreadable location is here.

Carries a regression test for the attributed call and for the default
path staying silent.

Part of #78
Every package directory now carries the Apache-2.0 text and a NOTICE
naming the copyright holder, so the built wheels include both; the README
linked to a LICENSE file that was not there, and the published wheels
contained no licence text at all.

Contributions are accepted under a Contributor License Agreement that
keeps the contributor's copyright and grants the holder the right to
distribute the work under any terms. A CLA Assistant workflow collects
signatures in the pull request and stores them on a dedicated branch.

Package metadata names the maintainer; the README's support section keeps
only the channel that exists.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T13:42:02.519858Z 8ac9031 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: d288bfdd-b6ac-45ac-809e-ba8f5f8208aa

📥 Commits

Reviewing files that changed from the base of the PR and between d04f46d and 8ac9031.

📒 Files selected for processing (20)
  • .github/workflows/cla.yml
  • CLA.md
  • CONTRIBUTING.md
  • LICENSE
  • NOTICE
  • README.md
  • coordinode-embedded/LICENSE
  • coordinode-embedded/NOTICE
  • coordinode/LICENSE
  • coordinode/NOTICE
  • coordinode/coordinode/_source.py
  • coordinode/coordinode/client.py
  • coordinode/pyproject.toml
  • langchain-coordinode/LICENSE
  • langchain-coordinode/NOTICE
  • langchain-coordinode/pyproject.toml
  • llama-index-coordinode/LICENSE
  • llama-index-coordinode/NOTICE
  • llama-index-coordinode/pyproject.toml
  • tests/unit/test_source_tracking.py
 ________________________________
< My GPUs mine bugs, not Crypto. >
 --------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/#90-licence-cla

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@polaz
polaz merged commit e23ef54 into main Sep 5, 2026
10 checks passed
@polaz
polaz deleted the chore/#90-licence-cla branch September 5, 2026 13:41
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

class _tracks_its_call_site: # noqa: N801 — reads as a decorator at the use site

P2 Badge Keep autospecced class patches callable

When tests use patch.object(AsyncCoordinodeClient, "cypher", autospec=True), unittest.mock reads the raw value from the class dictionary, sees this non-callable _tracks_its_call_site descriptor, and installs a NonCallableMagicMock; invoking client.cypher(...) then raises TypeError: 'NonCallableMagicMock' object is not callable. The added whole-class create_autospec(..., instance=True) test does not cover this common class-method patching path, so the descriptor needs to remain callable/autospec-compatible.


path = location.file if os.path.isabs(location.file) else os.path.abspath(location.file)

P2 Badge Treat call-site normalization failures as missing locations

When a query originates from dynamically compiled code with a relative co_filename and the process's working directory has meanwhile been removed, os.path.abspath() raises FileNotFoundError here. Because _source_metadata() does not catch errors from is_call_site(), enabling this debugging feature makes every such query fail before its RPC is sent, contrary to the module's failure-safe behavior; normalization failures should result in an unattributed query instead.


# The copyright holder and the project's own automation do not sign.

P2 Badge Restrict the CLA allowlist to known accounts

The bot* wildcard exempts every GitHub account whose login begins with bot, not only the project's automation named in the comment. A contributor using such an account can therefore receive a passing CLA status without accepting the agreement, undermining the workflow's stated purpose; enumerate the actual automation accounts instead of allowing an open-ended username prefix.



P2 Badge Pin the write-privileged CLA action to a commit

This pull_request_target job passes a token with write access to repository contents, actions, pull requests, and statuses into a third-party action selected by the mutable v2.6.1 tag. If that tag is moved or the upstream release is compromised, the replacement code runs on the next PR event with those write privileges; the repository's other workflows avoid this exposure by pinning actions to full commit SHAs, so this action should be pinned the same way.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@github-actions github-actions Bot locked and limited conversation to collaborators Sep 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: ship licence files and add a contributor agreement

1 participant