Skip to content

Add guarded existing-channel project binding - #150

Merged
andrei-hasna merged 3 commits into
mainfrom
fix/11d00f53-bind-existing-project-channel
Aug 10, 2026
Merged

Add guarded existing-channel project binding#150
andrei-hasna merged 3 commits into
mainfrom
fix/11d00f53-bind-existing-project-channel

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add exact preconditioned bind-existing registration for a stable channel without recreating it
  • preserve members, messages, metadata, description, topic, tags, and retain immutable prior ownership state for conditional inverse
  • cover SQLite/PostgreSQL storage, HTTP/OpenAPI, generated SDK, CLI, exact lookup/readback, idempotency, conflict refusal, pagination, and rollback

Verification

  • affected tests: 40 pass, 0 fail
  • full suite: 1825 pass, 0 fail
  • bun run typecheck
  • bun run build
  • bun run kit:check
  • SDK regeneration hash stable
  • git diff checks clean
  • gitleaks staged and origin/main..HEAD range scans: no leaks

Coordination

  • Todos task: 11d00f53-f016-40d4-bad7-a5bd36ee4b39
  • exact candidate head: 2135027
  • reviewer assignment intentionally left to the coordinator-fixed reviewer set; this PR is not approved for merge yet.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] NO_GO — exact candidate head 2135027, tree 7a2a24b88993542be41940699b167ed91863776d, base cd5cd2e. Blocking P1: the new operation-specific surfaces do not bind request intent to the selected operation. src/cli/commands/project-registration.ts:105-133 routes both create and bind-existing to the same registerProjectChannel call without asserting or rejecting bind_existing, and src/server/api.ts:853-866 does the same for POST /v1/project-registration/channels and POST /v1/project-registration/channels/bind-existing. Reachable CLI probe: project-registration bind-existing was given a valid ordinary expected-absent create request; exit 0 returned outcome=accepted, created_by_operation=true, prior_state=null, target_id=chn_05668762bce2e0958c4c3b0ac9da4c67, and the next collection read returned item_count=1. Thus a caller selecting bind-existing can create a new channel instead of safely rejecting the missing bind preconditions, violating the no-recreate bind contract, operation-specific API/SDK/CLI behavior, and safe mismatch rejection. Required focused fix: make bind-existing reject any request without the exact bind_existing envelope, make create reject bind_existing requests, and add negative CLI/API coverage plus affected direct-regression tests. Re-review is limited to this defect, its fix, and direct regressions. Passing evidence on this head: affected matrix 41 pass/0 fail; full suite 1825 pass/0 fail; typecheck rc=0; isolated build rc=0; contracts kit check rc=0; SDK regeneration stable at sha256 29353561c3c87fbf43ff833808a99b1139880a5d1e89710e5b05e9fc08553a98; gitleaks range scan rc=0 with no leaks; git diff --check rc=0; CI ci=SUCCESS and swift (macOS)=SUCCESS, with [code]smith=SKIPPED.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW AMENDMENT — same fixed reviewer, same initial pass] NO_GO remains authoritative on unchanged head 2135027. The finite blocking set is amended to TWO reachable in-scope P1 defects. P1-1 operation intent: create and bind-existing CLI/HTTP surfaces route to the same registration method without rejecting the opposite request shape; the bind CLI accepted an expected-absent create request and created a channel. P1-2 legacy message ownership: the candidate changes only channels.project_id. Live read-only task evidence records 237 dubai-fraud messages still owned by legacy project 1217f372-08e4-4217-aaf0-1ace5232982f, target_count=0 and new-workspace item_count=0. Source confirms the dead end: project-channel-registration message readback rejects every row whose project_id differs from the new workspace, while buildProjectMessageLinkagePlan rejects non-null conflicting project IDs and applyProjectMessageLinkage updates only project_id IS NULL rows. Therefore fixing P1-1 alone cannot produce the required non-empty complete one-to-many readback. Required focused remediation for P1-2: one atomic guarded exact legacy-project-to-workspace message ownership transition, with explicit old/new ownership preconditions, immutable receipt and conditional inverse/rollback, preserving message IDs, UUIDs, content, reply topology, count and every unrelated field; conflict, drift, idempotency, injected-failure and rollback tests in SQLite and PostgreSQL plus affected API/CLI/SDK contract coverage. The same reviewer will re-review only P1-1, P1-2, their fixes and direct regressions. Acceptance ends when both named paths pass their exact affected gates and no concrete reachable P0/P1 remains in those two lanes; no other reviewer or broad review is part of this cycle.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] NO_GO — #150 @ 2135027 — lens: correctness+isolation+wiring, reviewer codewith-sol-reviewer (1 of 1)

P0: None.

P1: src/cli/commands/project-registration.ts, src/server/api.ts, and src/lib/project-channel-registration.ts do not bind the selected operation to request intent. Both CLI verbs call registerProjectChannel, and both HTTP routes call registerProjectChannelPg; validation chooses create versus bind solely from the body. Therefore a valid expected-absent create request sent through bind-existing creates a channel, while a bind request sent through create mutates ownership. These documented operation-specific surfaces can perform the opposite write instead of rejecting it.

P1: src/lib/project-channel-registration.ts changes only channels.project_id during bind and its new test expressly preserves legacy messages.project_id values. The same file's listProjectChannelMessagePage rejects any message whose project differs from the newly bound channel. src/lib/project-message-linkage.ts cannot repair this state: buildProjectMessageLinkagePlan rejects non-null conflicting legacy project IDs, and apply updates only NULL project IDs. Thus the tested reachable case—an existing legacy-owned channel with history—returns an accepted bind but leaves all existing messages inaccessible through the new workspace collection.

P2: src/lib/project-channel-registration.ts and src/server/project-channel-registration-pg.ts compute inverse guards by reading and ordering every message in a channel. This is a non-blocking scale follow-up pending realistic-volume plans.

P3: None.

Could not verify: exact-head tests locally, a fresh PostgreSQL migration replay, or 10k–100k-row SQLite/PostgreSQL EXPLAIN plans; the supplied exact head was not present in the local checkout. I did not merge or modify any repository.

Enforce distinct create and bind-existing intent across CLI, HTTP, store, OpenAPI, and SDK surfaces. Transition exact-channel message ownership atomically with channel binding and preserve deterministic rollback evidence.

Agent: Theophrastus
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] NO_GO — #150 @ 51fa6cf — lens: correctness+isolation+wiring, reviewer codewith-sol-reviewer (1 of 1)

P0: None found.

P1 — src/lib/project-channel-registration.ts and src/server/api.ts: the candidate makes operation_intent=create mandatory on the existing create path, breaking the currently installed Projects producer. @hasna/projects 0.1.121 is installed on this machine, and its dist/cli/index.js calls registerProjectChannel(withTargetDigest(request)); that request has no operation_intent field. The new server route calls assertProjectChannelRegistrationOperationIntent(request, "create"), and the shared forward validator also rejects an absent intent. Therefore the existing authenticated cloud project-registration path will fail before creating any channel as soon as this Conversations server is deployed. This is a reachable cross-package compatibility/wiring regression, not a speculative old-client case. Preserve the legacy create envelope as create on the create-only endpoint (while still rejecting any bind intent/shape), or land an atomic Projects producer update before this backend becomes active.

P2: None recorded.

P3: None recorded.

Could not verify: exact-head tests locally because the supplied head object was absent from local checkouts; a fresh PostgreSQL migration replay; or 10k–100k-row SQLite/PostgreSQL EXPLAIN plans. The supplied status summary reports ci and swift (macOS) successful. I did not merge or modify any repository.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] NO_GO — focused remediation re-review

Exact candidate: base cd5cd2ef39e64ec2c5fd910fbddcf82d234becdd, head 51fa6cf386f143ef81b485c5f70b9f1d9774ad42, tree 2f7077a4159a48c85b4e13138657ae727e089c4a. The worktree was clean and GitHub reported this exact head/base as CLEAN and MERGEABLE.

The message-ownership remediation is accepted: the SQLite/PostgreSQL, CLI/API/SDK, rollback, immutable-receipt, injected-failure, idempotency, and drift tests cover the named legacy project-to-workspace transition while preserving message IDs/UUIDs/content/replies/count and unrelated fields.

One direct P1 regression remains in the operation-intent remediation:

  • P1 — established create requests without operation_intent are now rejected. The pre-remediation create contract accepted an expected-absent create request without that field. At the current head, the same valid legacy request through conversations project-registration create exits 1 with the literal error project channel registration create surface requires operation_intent=create. Against the initial reviewed head 2135027ef0ae7b38a80e4a300c8f63e8e578730c, the same request exits 0 and returns an accepted create receipt (outcome: accepted, created_by_operation: true). The current validator and HTTP create handler impose the same rejection, and the generated SDK now requires the discriminator, so this is a reachable CLI/API/SDK compatibility break, not a type-only concern.

Focused fix required: preserve the established expected-absent create request when operation_intent is omitted, while continuing to reject an explicit opposite intent and every bind-existing shape on the create surface. Add focused compatibility tests for the legacy CLI/API/SDK create request plus the existing negative intent tests. Re-review remains limited to this defect, its fix, and direct regressions.

Current-head focused gates:

  • bun test src/lib/project-channel-registration.test.ts src/server/project-channel-registration-pg.test.ts src/cli/project-registration.e2e.test.ts src/sdk/project-channel-registration.test.ts28 pass, 0 fail, 289 expect() calls.
  • bun run typecheck → exit 0.
  • bun run kit:checkok storage-kit check (expected v0.4.2).
  • isolated bun run build → exit 0.
  • isolated bun run sdk:generate reproduced the committed SDK digest ffbeecd7995953192b9c41daed4166965760ac7405078526d54ebe3513dd7212 and reported ok generated SDK -> src/sdk/index.ts (33 operations).
  • gitleaks git . --log-opts='cd5cd2ef39e64ec2c5fd910fbddcf82d234becdd..51fa6cf386f143ef81b485c5f70b9f1d9774ad42' --redact --no-banner2 commits scanned, no leaks found.
  • GitHub ci and swift (macOS) checks are SUCCESS at this exact head.

P2/P3, optional hardening, documentation polish, and unrelated concerns are non-blocking and were not searched or relitigated in this focused pass.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] GO — focused remediation cycle 1 complete

Exact candidate: base cd5cd2ef39e64ec2c5fd910fbddcf82d234becdd, head c7adc358b7e4566a94fa39c1e9f79adb88f948b5, tree a87d99cd193f4752e7e69e40f39cd330488ebdeb.

The one remaining P1 compatibility blocker is fixed without reopening unrelated scope:

  • Omitted operation_intent is accepted only on the create path when the request has no bind-existing shape and carries the exact expected-absent precondition digest. Final forward validation still checks the complete legacy envelope, desired/request digest, identity, bounds, and exact precondition.
  • A manual current-head CLI differential rerun of the same legacy request that failed at the prior remediation head returned RC=0, outcome: accepted, and created_by_operation: true.
  • The same omitted-intent legacy create request on bind-existing returned RC=1 with project channel registration bind_existing surface requires operation_intent=bind_existing.
  • Explicit operation_intent=bind_existing on the create surface returned RC=1 with project channel registration create surface requires operation_intent=create.
  • Focused CLI/API/SQLite/PostgreSQL tests also reject bind-existing shapes without intent and preserve the explicit opposite-intent rejections.
  • OpenAPI now makes only create intent optional; bind intent and bind preconditions remain required. The generated SDK exposes operation_intent?: "create" for create and retains required operation_intent: "bind_existing" plus bind_existing for bind.

Direct-regression verification:

  • bun test src/lib/project-channel-registration.test.ts src/server/project-channel-registration-pg.test.ts src/cli/project-registration.e2e.test.ts src/sdk/project-channel-registration.test.ts scripts/generate-sdk.test.ts printed 31 pass, 0 fail, 322 expect() calls.
  • Those affected lanes retain the already-accepted atomic message-ownership transition, including SQLite/PostgreSQL binding, CLI/API/SDK routing, exact ownership preservation, immutable receipts, idempotency, failure rollback, inverse restoration, and drift refusal.
  • bun run typecheck exited 0.
  • bun run kit:check printed ok storage-kit check (expected v0.4.2).
  • Isolated bun run build exited 0.
  • Isolated SDK regeneration printed ok generated SDK -> src/sdk/index.ts (33 operations); the before/after SHA-256 was exactly 795d968caa226e4d96ac99c012b5cc0f645bdba2b785aa590c6cb4bb9867c148.
  • Staged scan printed 0 commits scanned and no leaks found; the base-to-head range scan printed 3 commits scanned and no leaks found.
  • git diff --check 51fa6cf386f143ef81b485c5f70b9f1d9774ad42..c7adc358b7e4566a94fa39c1e9f79adb88f948b5 exited 0.
  • After fetching origin, the worktree was clean, origin/main remained the named base, and GitHub reported this exact head/base OPEN, CLEAN, and MERGEABLE, with ci and swift (macOS) both SUCCESS.

Verdict: GO. Zero concrete, evidence-backed, reachable in-scope P0/P1 defects remain in the compatibility fix or its direct regressions. P2/P3, optional hardening, documentation polish, and unrelated unchanged code are non-blocking and were not searched or relitigated.

@andrei-hasna
andrei-hasna merged commit ec2d1b9 into main Aug 10, 2026
3 checks passed
andrei-hasna added a commit that referenced this pull request Aug 10, 2026
Release @hasna/conversations 0.5.44 with the guarded existing-channel adoption and atomic message-history transition from #150.

Agent: Theophrastus
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.

1 participant