Skip to content

feat(coordination): client-neutral coordination read API and read-only listener (CB-10) - #186

Open
avalla wants to merge 21 commits into
mainfrom
feat/cb-10-coordination-api
Open

avalla wants to merge 21 commits into
mainfrom
feat/cb-10-coordination-api

Conversation

@avalla

@avalla avalla commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds the M23 client-neutral coordination read API (CB-10): one application service over the CB-02 planning read model, exposed by the coordination:read CLI command and by an optional, read-only coordination listener on a second Unix socket (ADR-0036 Decisions 5 and 7). Later adapters (GitHub bridge, MCP server) use the same service through that listener; none recomputes the read model or has an authority path of its own.

Task

  • Task: 3a112f30-fc68-4ccd-9d1b-874148287f6c — CB-10 Client-neutral coordination API (requirement CB-R10)
  • Acceptance criteria (Runtime task text and CB-R10):
    • Read operations over the CB-02 read model: planning status, milestones, tasks, requirements, dependencies and blockers, runnable work (status, milestones, tasks, requirements, dependencies, blockers, runnable)
    • Exposed as a CLI command and through a dedicated read-only Runtime listener serving a coordination route profile: GET only, /api/coordination/v1/... and a minimal health query, no /commands, no dashboard routes, disabled by default
    • Exposure registry and explicit mappers, data classes P and T
    • One application service, no duplicated service, no adapter-specific authority path
    • Parity test: listener, CLI and service return byte-identical data for the same query
    • Tests that no command or dashboard route is reachable on the listener
    • Final operation names after contract review: deferred by CB-R10 itself to the CB-11A contract review (names live in one place, coordinationReadOperations)

Scope

  • Application: coordination-exposure.ts (registry, mapper, credential scrubber, 256-code-point truncation with truncatedFields), coordination-read-service.ts (operations, optional allowed-class policy, 256 KiB response cap), coordination-listener-configuration.ts (closed-schema parser), GetPlanningSnapshot.readSnapshot (extracted, behaviour unchanged).
  • Protocol: coordination-query-protocol.ts (prefix, route and parameter grammar, limits, error codes; pure, for the CB-11B query client).
  • Daemon: coordination-query-api.ts (thin HTTP transport that never falls through), second Bun.serve({ unix }) in the host, configuration file loader.
  • Storage: the SQLite planning read refuses to run inside an open writer transaction on the shared connection (PlanningReadBusyError → RUNTIME_BUSY), so no adapter can observe uncommitted state.
  • CLI: coordination:read, help line.
  • Docs: architecture overview section, roadmap M23 paragraph, coordination architecture status line.
  • Non-goals: MCP tool names and schemas (CB-11A), query-client extraction (CB-11B), principal projection route (CB-11C), isolated deployment, other socket ownership or modes (CB-11D). No migration, no dependency, no network listener, no write operation.

Dependencies

Design notes

  • Listener configuration: <runtime home>/coordination-listener.json, {"version":1,"enabled":true,"projects":[{"projectId":"…","classes":["P","T"]}]}, read once at start. Missing means disabled. Invalid, oversized, symlinked or unreadable means misconfigured: the listener stays closed and the Runtime runs unchanged.
  • Socket <runtime home>/coordination.sock, mode 0600. Only a stale socket is replaced, never another file at that path. A bind failure leaves the listener closed and never the Runtime down.
  • A project that is not enabled answers 404 Project not found, exactly like a missing one. Requested classes must be a subset of the enabled ones.
  • The listener appends no audit row on a read and receives no principal. coordination:read on the main socket records only the host's generic command envelope, as every command does.
  • Each collection response carries pageDigest over the whole exposed response except that field (same definition as CB-02, recomputed after mapping). snapshotId is the CB-02 identity.
  • Errors: INVALID_REQUEST 400, NOT_FOUND 404, METHOD_NOT_ALLOWED 405, PAYLOAD_TOO_LARGE 413/414, RUNTIME_BUSY 503 with retry-after, INTERNAL_ERROR 500 without detail.

Pipeline exception

The project's enforced delivery pipeline could not run as a binding Runtime pipeline:

  • the developer agent is routed to openai:gpt-6.1-sol and the reviewer to openai:gpt-5.5;
  • the Claude worker executes only Claude models, and the Codex worker admits only CLI 0.160.0 and model gpt-5.5 (codex-cli 0.163.0 is installed);
  • the gateway has no provider credentials or pricing, and a text-only result cannot satisfy Implement;
  • Review requires an independent operator approval.

Changing routing, the allowlist or credentials only to get past this was excluded by the owner. The work therefore follows in-session task-delivery with independent subagents, as for CB-01 and CB-02.

Verification

See the hardening update below (final head, checks and reviews).

Merge

Do not merge without explicit authorization. READY FOR MERGE is not DONE: post-merge verification is still required.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9

Hardening update (2026-10-19) - HEAD 21fb235

  • Credential scrubber: prefixed camelCase and acronym-prefixed assignment names (dbPassword, githubToken, rsaPrivateKey, awsSecretAccessKey, DBPassword, JWTSecret, GCPApiKey) are now redacted by a case-sensitive companion pattern that shares the value half of the existing one; the existing pattern is byte-identical (verified), and a single lower-case word such as mytoken= stays untouched. Negative tests cover prose, calls and short values.
  • Listener policy: explicit unit tests that the listener passes {kind: "exposure", projects} (the listener's own map) to the read service and that policy, kind or projects query parameters are rejected with 400 before any read. A mutation to the owner policy fails the test.

Evidence: bun run check exit 0 on 21fb235 (250 files, 3623 passed, 242 skipped); GitHub CI 13/13 green. Independent read-only review: no P1/P2; one P3 (acronym prefixes), fixed in 21fb235; no ReDoS found up to 50,000 characters.

Not in this PR, tracked in task 92cf4e55-aaf4-47b0-9d9a-42f79d6d1ac6: upper-case keyword glued to a prefix (dbPASSWORD), over-redaction of names like next_page_token, and a shared scanner design. The scrubber remains documented as best-effort.

External review (Codex): not satisfied on the final head. The Codex CLI and the GitHub connector are unavailable (usage limit); no other review stands in for it. The independent review below is by a subagent of the same model family, not Codex.

avalla and others added 2 commits October 16, 2026 09:49
One application service over the CB-02 planning read model, used by the
coordination:read CLI command and by an optional read-only coordination
listener on a second Unix socket (ADR-0036 Decisions 5 and 7).

- Exposure registry and explicit P/T mappers with a best-effort credential
  scrubber, 256-code-point truncation flagged per item, a 256 KiB cap.
- Operations status, milestones, tasks, requirements, dependencies,
  blockers and runnable; optional allowed-class policy.
- Listener disabled by default, enabled per project and data class in
  <runtime home>/coordination-listener.json; GET-only coordination routes
  and a minimal health route, no commands or dashboard routes, no audit
  row on a read; an invalid file keeps it closed and the Runtime up.
- The SQLite planning read refuses to join an open writer transaction on
  the shared connection (RUNTIME_BUSY) instead of reading uncommitted state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 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-10-16T09:37:13.161486Z b360371 Manual request
ℹ️ 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.

avalla and others added 2 commits October 16, 2026 10:21
- Never take over the main socket or a socket that still answers.
- Enforce the per-project exposure policy in CoordinationReadService.
- Cut relationship lists at 100 entries so one item always fits.
- Extend the credential scrubber (Stripe, GitLab, npm, Google OAuth,
  Basic auth, URL userinfo, secret assignments, zero-width splits),
  bound scanned text and never leave a partial redaction marker.
- Retry a planning read that meets a short writer transaction.
- Bound concurrent listener reads, log internal failures by type only,
  create the socket owner-only, stop it without awaiting slow clients.
- Read the configuration with O_NOFOLLOW and require a regular file
  owned by the Runtime user and not writable by others.
- runtime:purge removes a stale coordination socket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
@avalla

avalla commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b4ed484a9

ℹ️ 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".

avalla and others added 2 commits October 16, 2026 10:47
- The coordination snapshotId is the digest of the exposed projection,
  so a scrubbed credential or a cut title tail cannot change it.
- runtime:purge refuses while coordination.sock accepts connections.
- Only a refused connection marks the coordination socket stale.
- Open the listener configuration with O_NONBLOCK so a FIFO cannot
  block Runtime start.
- Close the listener before stopping: new reads answer RUNTIME_BUSY.
- Scrubber: keep text without a credential unchanged, quoted keys and
  values, more Slack prefixes and invisible characters, bounded
  code-point conversion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
@avalla

avalla commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: a5d4d5469e

ℹ️ 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".

avalla and others added 2 commits October 16, 2026 11:04
…e the projection

- Requirements in the exposed projection are ordered by exposed key, so
  a scrubbed key cannot reveal where its stored value sorts.
- The scrubber redacts quoted values with escapes and unterminated ones.
- The exposed projection is cached per CB-02 identity, so unchanged
  state is mapped and hashed once across pages and status reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
@avalla

avalla commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 635aba8c83

ℹ️ 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".

An unterminated quoted value after a secret keyword is now redacted to
the end of its line, escapes included, so a multi-word passphrase cannot
leave its tail behind. Also reattach the exposedProjection docblock and
name the cache test after what it checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
@avalla

avalla commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cda876fd04

ℹ️ 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".

avalla and others added 3 commits October 16, 2026 11:24
…ned items

- An unterminated quoted value ending in a lone backslash is redacted.
- Collection pages return detached copies, so changing a returned item
  cannot change the cached exposed projection served to later reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
Milestones kept the read model's creation-time order, a value the
coordination exposure never returns, so swapping only creation times
changed page order, snapshotId and pageDigest. Exposed milestones are
now ordered by id and the protocol lists creation times among the
values the exposed identity does not depend on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
…alues

Pin single quotes with CRLF, odd backslash runs, an escaped closing
quote at the end of a line, the accepted redaction of an empty
unterminated value, and a bounded scan of a long backslash run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
@avalla

avalla commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: b360371f36

ℹ️ 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".

avalla and others added 6 commits October 16, 2026 11:38
A keyword after an underscore, as in DB_PASSWORD or GITHUB_TOKEN, now
starts an assignment, and an escaped line break no longer ends a closed
quoted value early, so the rest of the value is redacted with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
…ating

Both stored keys sorted after "R-2", so the test passed without the
exposed-key sort. One key now sorts before it and one after. The
protocol no longer claims that list entries past 100 leave the
identity unchanged: counts still reflect the uncut relationships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMasEtuNxnMyf4ZRrFndu9
….4 and harden socket tests

Bun 1.3.6 reports ENOENT, not ECONNREFUSED, when connecting to a stale
socket file, so the listener never replaced it. ENOENT now counts as stale
only while the path is still a socket. Socket-holder tests wait until the
holder accepts connections, keep paths within the macOS sun_path limit and
pass the path through the environment instead of building script text from
it (CodeQL js/bad-code-sanitization).

Claude-Session: https://claude.ai/code/session_01Q21NUKiVwrCvpZ5YgzjM5W
…dService.execute

The policy parameter defaulted to the owner policy, so an adapter that omitted
it would silently gain every project and the P and T classes. The contract now
requires the caller to state its authority: the CLI passes owner, the listener
passes its exposure policy, and tests pass an explicit policy. A regression
test pins that a caller omitting the policy gets no data.

Claude-Session: https://claude.ai/code/session_01FKM37BV4LS8Rc8ny5GCwXy
…nd error

Review hardening: an omitted or malformed policy was refused only because
reading its kind threw a TypeError. The service now refuses it explicitly with
ProjectNotFoundError and the regression test asserts that type.

Claude-Session: https://claude.ai/code/session_01FKM37BV4LS8Rc8ny5GCwXy
@avalla

avalla commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

Update for head 0a5301c: ChatGPT P2 confirmed and fixed. CoordinationReadService.execute now requires an explicit CoordinationReadPolicy (CLI passes owner, listener passes exposure, tests pass explicit policies); an omitted/unknown policy is refused with ProjectNotFoundError and a regression test pins it. Evidence: bun run check exit 0 (3619 tests), CI green (13/13), independent review APPROVE (no P1/P2). Codex: one static review from the piped diff text (codex review -, repository access blocked by bubblewrap in this environment), no findings. This is not a sandboxed-repository Codex review; recorded as such.

@avalla avalla left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent ChatGPT review — HEAD 0a5301c (static source review, not Codex CLI)

Technical verdict: no newly confirmed P1/P2 findings in the reviewed code. Reviewed the coordination application service, policy enforcement, CLI and listener call sites, route parser, exposure mapper, SQLite planning-read adapter, listener startup, and purge guard. CoordinationReadService.execute now requires an explicit policy; the CLI supplies owner, while the listener supplies project-scoped exposure. An absent/unknown runtime policy fails closed. The listener has no command or dashboard fallthrough, and the purge code refuses on a live or inconclusive coordination socket. Redacted projection data is used to derive the coordination snapshot digest.

P3 follow-up (non-blocking): consider a direct listener unit assertion of the passed exposure policy, in addition to the existing behavioural e2e coverage. The text-scrubber is documented as best-effort, not a general secret-scanning guarantee.

Evidence: GitHub workflows on the published HEAD are green. I did not run Bun locally, so this review relies on source inspection and CI records. The PR description's consolidation note still names an earlier HEAD and should be refreshed.

Gate: This review is not codex review --base origin/main; leave the configured external-review gate distinct.

…ener policy

- The assignment scrubber required a non-alphanumeric character before the
  keyword, so prefixed camelCase names such as dbPassword, githubToken or
  rsaPrivateKey were exposed. A case-sensitive companion pattern now matches a
  capitalised keyword that follows a lower-case letter or digit; a single
  lower-case word such as mytoken stays untouched.
- Add a direct assertion that the listener passes its per-project exposure
  policy to the read service, and that a client cannot select the owner policy
  or widen the enabled set through query parameters.

Claude-Session: https://claude.ai/code/session_01FKM37BV4LS8Rc8ny5GCwXy
An upper-case acronym running into the keyword (DBPassword, JWTSecret,
GCPApiKey) was not redacted because the camelCase pattern required a lower-case
letter or digit before it. Accept any letter or digit; keywords stay
capitalised, so a single lower-case word such as mytoken is still untouched.

Claude-Session: https://claude.ai/code/session_01FKM37BV4LS8Rc8ny5GCwXy
@avalla

avalla commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

Hardening cycle closed on 21fb235: camelCase and acronym-prefixed credential names are redacted (negative cases covered), and the listener policy has an explicit unit test (a mutation to the owner policy fails it). bun run check exit 0 (3623 passed), CI 13/13. Residual scrubber items are in task 92cf4e55 (best-effort scrubber, not a guarantee). The CodeQL, purge-guard and snapshot-identity threads were already resolved with evidence. Codex gate: not satisfied, not replaced by this review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants