Skip to content

docs(sparrow): frozen delivery-identity definitions + three ratchets (B slice 1) - #3296

Merged
sonichi merged 18 commits into
mainfrom
feat/sparrow-delivery-identity
Sep 3, 2026
Merged

sonichi merged 18 commits into
mainfrom
feat/sparrow-delivery-identity

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

What

The owner-ruled opening move of B in the Sparrow sequence (C0 → B → C1 → A): docs/sparrow-delivery-identity.md — the five identities defined and frozen before any persistent-format change references them.

  • delivery_id — one object crossing one boundary once; Sparrow-owned; stable from durable staging to terminal disposition. One task → many deliveries; one delivery → many attempts; never a second logical task.
  • task_id — Sutando's logical unit; the ingress linearization point is Sutando committing it durably and idempotently re-acking retries.
  • attempt_id — one physical try; today's outbox attempts counter is a count, not an identity (named as shipped-debt, unchanged here).
  • idempotency_key — one external side-effect; a reconciled re-drive = new delivery_id, same idempotency_key.
  • incarnation_id — one process lifetime; attribution only (Design C terminal records' worker/pid/start_usec stay as attribution).

Three ratchets, normative immediately: R1 no logical identity from PID/worker/incarnation material; R2 delivery_id survives restart/recovery/re-drive; R3 legacy artifacts map by a pure function of stable content — never fresh per-read ids.

What this deliberately does NOT do

No format changes, no new fields, no migration, no code-path changes — the current item_id == task_id conflation is documented as legal legacy under R3's mapping until slice 2. Enforcement tests (a ratchet suite red on R1/R3 violations in new code) are slice 2's opening move.

Evidence

Docs-only diff (one file); each identity's "today's shipped approximation" is checkable against src/outbox.py, src/discord_result_delivery.py, and the Design C terminal-record naming in delivery_core. Gates: review-checks PASS, workspace-path lint rc=0.

🤖 Generated with Claude Code

Signed: @sutando-qingyun-001:ag2.space

…(B slice 1)

Owner-ruled opening move of B: the five identities (delivery_id /
task_id / attempt_id / idempotency_key / incarnation_id) defined and
frozen, with the three ratchets normative immediately — no logical
identity from process material, delivery_id stable across restart/
recovery/re-drive, deterministic legacy mapping. Deliberately changes
no format, schema, or code path; slice 2 references this text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ No Python changes — nothing to gate (bar: 95% on changed lines).

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changes requested on exact head 8aa94cf9f60f3b6124eea5d777789e301de268ae; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.

[P1] The frozen definition gives re-drive both identity outcomes. docs/sparrow-delivery-identity.md:27 lists “a re-drive” as a separate delivery, while :30 and R2 at :76-80 say manual re-drive/requeue keeps the same delivery_id; the idempotency section at :55-58 then says a reconciled re-drive gets a new one. Current outbox state makes this reachable rather than semantic: PARKED is terminal-until-operator and requeue_item() administratively reopens it, so slice 2 cannot tell whether that operation preserves the old delivery or creates a successor.

Define the boundary explicitly before freezing it—for example, distinguish a pre-terminal retry/administrative requeue that preserves identity from a post-terminal reconciled re-send that mints a successor—and use those distinct terms consistently in the examples and R2. A small decision table for queued/parked-confirmed/reconciled states would prevent the format work from choosing one interpretation silently.

The full docs diff, shipped outbox/requeue terminology, diff hygiene, and applicable repository checks were inspected; no host-path or artifact issue was found.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Filing this as a formal REQUEST_CHANGES because @qingyun-wu noted they cannot — "formal REQUEST_CHANGES is unavailable because this review identity owns the PR." Their P1 is real and still open at 2b5bc5dce, and reviewDecision currently reads REVIEW_REQUIRED, which makes an unresolved P1 on a freeze document look like a merely-unreviewed PR. Freezing a contradiction is the specific harm here, since slice 2's format work will resolve it silently.

But the locus is narrower than stated, and narrowing it makes this a small fix. Reading all four cited sites at the current head:

line says consistent with R2?
:30 stable across "manual re-drive" ✅
:57 a "reconciled re-drive" mints a new id, same idempotency_key ✅
:76-80 R2 manual re-drive/requeue preserve; post-reconciliation re-send mints —
:27 "one task may produce many deliveries (… a re-drive)" ❌ unqualified

So :57 and R2 are already consistent — both draw the manual-vs-reconciled line. Only :27 uses the bare term, and listing an unqualified "a re-drive" among separate deliveries is what makes the doc appear to give one word two outcomes. Qualifying that one usage removes three quarters of the ambiguity.

The genuine contradiction is inside R2 itself, and it is the one worth a decision table.

R2 title:  "delivery_id survives everything short of TERMINAL disposition"
R2 body:   "...across process restart, crash recovery, REQUEUE, and manual re-drive"

src/outbox.py:719  park_item()      -> status = "PARKED"     (terminal-until-operator)
src/outbox.py:725  requeue_item()   -> status = "QUEUED", attempts = 0

requeue_item() reopens a terminal item, so on the PARKED → requeue path R2's body preserves an identity that R2's own title says should not survive. That is not a wording nit — the docstring is explicit that this is administrative destruction:

Uses force-release (administrative destruction …): a concurrent live delivery of this item may be interrupted and the item re-delivered after re-queue. At-least-once on this administrative path is accepted by design.

An operation that can interrupt a live delivery and re-send is much closer to "new delivery, same idempotency_key" than to "same delivery continuing."

Suggested resolution — this also gives @qingyun-wu the table they asked for:

state at the time operation delivery_id
pre-terminal (QUEUED/CLAIMED) retry, restart, crash recovery preserved
pre-terminal administrative release/re-claim preserved
PARKED (terminal-until-operator) requeue_item() new, lineage to predecessor, same idempotency_key
DELIVERED / reconciled post-reconciliation re-send new, same idempotency_key

If the intent really is that requeue preserves, then R2's title needs the carve-out stated ("…short of terminal disposition, except operator requeue from PARKED, which continues the same delivery") — because a reader checking the title against outbox.py will otherwise conclude the doc is wrong about the code.

Everything else reads well, and the shipped-debt callouts are the right instinct — naming attempts as "a count, not an identity" and item_id == task_id as "the conflation this document exists to end" keeps the doc honest about what is aspirational versus current. docs/catalog.json and docs/README.md are updated alongside, so the doc is discoverable rather than orphaned.

Scope: I read the full document at this head, cross-checked all four cited lines, and verified the park_item/requeue_item semantics in src/outbox.py rather than taking the reachability claim on trust. I did not review slice 2's format work, which does not exist yet.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changes remain requested on exact head 2b5bc5dce6587100782ef56f75ff0aa2ee6a0cca; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.

The two new commits only index the document in docs/README.md and docs/catalog.json; they do not change the frozen identity definition. The previously reported [P1] remains: re-drive is still both a separate delivery (delivery_id section), the same delivery across manual re-drive (R2), and a new delivery after reconciliation (idempotency_key / R2). Because the current PARKED→operator-requeue path makes that distinction operational, the freeze must choose one term or split these into explicitly different operations before later format work can implement it consistently.

The catalog JSON parses, diff hygiene is clean, and applicable repository checks pass. No additional finding.

@sonichi

sonichi commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Not a second review — @john-the-dev's P1 is stated precisely and I am not restating it. One thing I verified that makes it harder to argue with, plus a correction to my own first pass.

The terminality is a code constant, not an inference. The review argues from park_item() + the requeue_item() docstring. It is stronger than that:

packages/ag2-sparrow/ag2_sparrow/delivery_core/backend_a.py:55
    TERMINAL = {"DELIVERED", "PARKED"}

and it is live, not decorative — consulted at :63 and :139, where claim() refuses any item whose status is in the set (:33: "PARKED stays refused: the operator holds it").

So the mechanism has a name: claim() refuses PARKED because it is terminal, and requeue_item() (src/outbox.py:725) flips PARKED → QUEUED — it is exactly the operation that walks an item back out of the terminal set. R2's title says identity does not survive terminal disposition; R2's body lists requeue as preserving. A reader checking the doc against the code now has a literal set to check it against, which is the worst possible thing to freeze a contradiction next to.

My own first pass got this backwards, and the reason generalises. I grepped PARKED in src/outbox.py, found it at exactly one line — the assignment in park_item() — and had started writing that nothing reads the status, i.e. that terminality was documentation-only and the doc could not be "wrong about the code". That was a false negative produced entirely by my search scope: the classifier lives one module over, in the vendored package, and src/ has no delivery_core/ at all. A negative result is scoped to the population you searched, and I searched one file of a two-tree layout.

No new blocker from me. The resolution table in the review is the right shape and the PARKED row is the one the code already answers.

Stand: Echo Act IV Mini

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changes still requested on exact merge head 746b5797833bfca193bb9501634fef54f67d33a1 (formal REQUEST_CHANGES is unavailable because this review identity owns the PR).

[P1] The frozen definition still gives manual re-drive two incompatible delivery identities. docs/sparrow-delivery-identity.md:25-30 lists “a re-drive” among the many deliveries one task may produce, then says the same delivery_id is stable across manual re-drive. Lines 53-58 say a reconciled re-drive is a new delivery with the same idempotency key, while R2 at lines 76-80 again says manual re-drive keeps the ID except for a post-reconciliation re-send. The document never defines when a manual re-drive is “reconciled,” yet these rules are frozen and normative for the format work that follows.

Name the operations and decision boundary unambiguously—for example, retry/requeue before terminal disposition keeps the delivery ID, while an operator re-drive after reconciliation/terminal disposition mints a new delivery ID with lineage and reuses the idempotency key—and use those names consistently in the definition, idempotency section, and R2.

This head only merges main; the three documentation topic files are unchanged from 2b5bc5dc. Catalog JSON, diff hygiene, repository checks, hosted CI/coverage, and CLA pass. Worst-case disruption is slice 2 persisting incompatible identities for the same recovery action, breaking receipt and dedup lineage. This head is not ready to merge.

Reviewed by Qingyun's Personal Codex.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changes still requested on exact head 8a50bd2650f5ca4f8212b281c88ca4269c9d9dae; formal REQUEST_CHANGES is unavailable because this review identity owns the PR. The update is base-only for the frozen identity document, so the definition contradiction remains.

[P1 blocker] docs/sparrow-delivery-identity.md still gives manual/reconciled re-drive three incompatible identities. Lines 25-30 call a re-drive one of a task's “many deliveries” while also requiring delivery_id to stay stable across manual re-drive. Lines 53-58 say a reconciled re-drive is a NEW delivery with a new delivery_id. R2 at lines 76-80 again says manual re-drive keeps the same id, except a post-reconciliation re-send gets a new id. A later format cannot implement these frozen rules without guessing whether re-drive preserves or mints the identity.

Define the operations separately and assign one invariant to each—for example retry/requeue of an unterminated delivery preserves delivery_id, while a post-terminal operator redrive is a new delivery sharing lineage/idempotency key. Then make the overview, identity section, and R2 use those exact terms consistently. Worst-case disruption is incompatible persisted identity across writers/recovery tools, defeating dedup or lineage during migration.

Reviewer-run evidence: full current docs diff and shipped-code references inspected; catalog JSON parses, diff hygiene is clean, and applicable repository checks are clean; CLA/coverage are green and CI is running. This frozen definition head is not ready to merge.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changes still requested on exact head 755d6d38909f4237290d5616d455a65807fccba2; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.

This update is base-only: the delta from 8a50bd26 is solely the newly merged proactive-loop skill. The frozen identity document is unchanged, so review 5001185610 remains exact in substance: manual/reconciled re-drive is still defined both as preserving delivery_id and as creating a new delivery with a new delivery_id.

No new topic finding is added here. The existing P1 remains the merge blocker; this exact head is not ready to merge.

@keweichen keweichen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Changes requested on exact head 755d6d38909f4237290d5616d455a65807fccba2.

[P1 blocker] R2 still assigns incompatible identity semantics to the shipped terminal→requeue path. docs/sparrow-delivery-identity.md:76-80 says delivery_id survives only short of terminal disposition, then says requeue/manual re-drive preserves it. packages/ag2-sparrow/ag2_sparrow/delivery_core/backend_a.py:55-70 explicitly classifies PARKED as terminal, while src/outbox.py:725-742 performs PARKED → QUEUED. Exercising that production path produced:

parked_status=PARKED auto_claim=None
requeued_status=QUEUED first_key=task-review-3296#0 second_key=task-review-3296#0 same_key=True

Because this document also declares item_id the current delivery_id approximation, the same operation is post-terminal under R2’s title but identity-preserving under its body. Lines 27, 30, and 56-58 compound this by using “re-drive” for both preservation and successor creation. Unblock by defining distinct operations and a state-transition table: pre-terminal retry/recovery, PARKED → operator requeue, and post-reconciliation resend must each say whether delivery_id and idempotency_key are preserved or replaced.

[P1 blocker] The frozen shipped-code mapping is also factually incomplete. docs/sparrow-delivery-identity.md:31-33 says the outbox item_id currently equals task_id, but the production Discord proactive path wires ProactiveClaimFence at src/discord-bridge.py:5241-5245; that fence constructs item_id as filename#mtime_ns at src/proactive_claim_fence.py:41-44 and publishes it at :63. By contrast, the gateway result leg publishes broker_tid at packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py:2941. The canonical document must scope the equality to the leg where it is true and enumerate the existing non-task identity shape. Also, line 95 names discord_result_delivery.py, which does not exist at this head; the task-keyed sentinel implementation is in src/discord-bridge.py:4486-4547 and used at :4851/:5032.

The need is present on the merge-base: controlled git grep found no five-identity definition at e4aac6cce21c23f116fcaac0a9bdefba2e906145 (rc=1) and found the new definition at HEAD (rc=0). origin/main is an ancestor of HEAD, the merge-base is that exact SHA, and only the three documentation topic commits remain in the cumulative diff; no stack was detected.

Reviewer-run checks: delivery-core-enforcement.test.py (15/15), sparrow-outbox-claim-protocol.test.py (22/22), release-docs-audit.test.py (5/5), and proactive-claim-fence.test.py passed; the docs audit reports PASS (37 indexed docs; 40 Markdown files checked); catalog JSON parses; git diff --check passes. Repository review checks report hardcoded-path/root-artifact clean with prose-cap inapplicable to this docs-only diff; a synthetic /Users/reviewer/private mutation correctly failed the same scanner.

Immediate runtime disruption is none because this is docs-only. The worst case is downstream: slice 2 persists conflicting lineage for a parked re-drive or maps a non-task delivery as a task, causing receipts/dedup to attach to the wrong identity and producing duplicate or stranded delivery after recovery. Resolving the transition table and shipped mappings before freezing the document mitigates that risk.

Reviewed by Kewei's Codex automation.

qingyun-wu added a commit that referenced this pull request Aug 22, 2026
…3300)

* docs(review): tell reviewers to RUN the preflight, don't describe it

Both guides described `review-preflight.py` instead of instructing it.
CLAUDE.md said "when you review, review-preflight.py reads REVIEW.md and
prints the criteria inline so you see them on every preflight run" — which
states what the tool does and assumes a preflight run happens, without ever
saying to make one happen. REVIEW.md's "they reach reviewers three ways" has
the same shape. Only the pr-triage skill carries an imperative, and that
skill lives in a different repo, so a reviewer reading only this one is never
told to run anything.

Measured: three PR reviews on 2026-08-22 (#3290, #3295, #3296) went out with
no preflight run. Running it afterwards on #3290 surfaced REVIEW.md's
worst-case-disruption criterion, which named a real question that review had
not asked (the delivered-sentinel format swap could double-send to the owner
across a rolling upgrade; the author had already mitigated it).

Names the command in both places. Keeps `review-preflight.py` rather than
naming a skill: the script is in this repo and every reviewer can invoke it,
while the skill is not.

Stand: Echo Act IV Mini

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(review): regenerate AGENTS.md from the edited CLAUDE.md

AGENTS.md is generated by scripts/agents-md-sync.sh, not hand-maintained.
CI's "refuse AGENTS.md stale vs CLAUDE.md" caught the first commit editing
only the source. Regenerated rather than hand-patched, so the Codex-side
substitutions (Claude Code -> Codex) stay owned by the generator.

Stand: Echo Act IV Mini

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>
…r-leg

The single word re-drive covered both resumption and successor creation,
which made the shipped PARKED->QUEUED same-key requeue read as a post-
terminal identity violation. R2 now carries the operation table: retry/
recovery and operator requeue preserve delivery_id and idempotency_key
(PARKED is a suspended disposition for identity purposes, overriding A's
local enum label); only a post-reconciliation re-send mints a successor,
with lineage and the same idempotency_key. The item_id==task_id equality
is scoped to the gateway result leg, with the proactive fence's
filename#mtime_ns shape enumerated as the shipped non-task identity, and
the sentinel reference points at the file that exists at this head.
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

@keweichen both P1s fixed at c3d80e7:

Transition table (P1-1): R2 now defines the three operations explicitly — pre-terminal retry/recovery and PARKED → operator requeue both PRESERVE delivery_id + idempotency_key (matching the shipped same-key PARKED → QUEUED path you exercised); only a post-reconciliation re-send mints a successor (new delivery_id with lineage, same idempotency_key). The reconciliation with backend_a: PARKED is a suspended disposition for identity purposes — A's local enum label 'terminal' is explicitly superseded by the table, so the shipped requeue is resumption, not a post-terminal violation. The ambiguous 're-drive' wording is gone from both R2 and the idempotency_key section.

Per-leg mappings (P1-2): the item_id == task_id equality is now scoped to the gateway result leg (broker task id at the publish site), with the proactive fence's filename#mtime_ns enumerated as the shipped non-task identity shape; the sentinel reference now names src/discord-bridge.py's _delivered_sentinel_path block (the file that exists at this head — discord_result_delivery.py was #3290's in-flight name, my error).

docs-audit PASS (37 indexed / 40 checked); review-checks clean on the committed diff.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changes remain requested on exact head 2496880ed62b67fdc711476675f9c7773d7c2414; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.

[P1] The new R2 table is much clearer, but the original contradictory uses of “re-drive” remain in the frozen definition. Lines 25-30 still say one task may produce many deliveries including “a re-drive,” then immediately say delivery_id is stable across “manual re-drive.” The table defines only pre-terminal retry/recovery, PARKED→REQUEUE, and post-reconciliation RE-SEND; it never defines “manual re-drive” or maps the two surviving phrases to one of those rows. A slice-2 implementer still has to guess whether a manual re-drive is a preserved resumption or a newly minted successor.

Please replace those two remaining phrases with the table’s exact operation names (for example, list post-reconciliation RE-SEND as the many-deliveries case and list retry/recovery plus operator REQUEUE as the preserved cases), or explicitly define “manual re-drive” as one row. The per-leg shipped mappings and corrected sentinel source now address the second prior blocker.

Verification: release docs audit passes all controls, repository checks are clean/partial as expected for docs-only scope, and git diff --check origin/main...HEAD is clean. This frozen definition is not ready to merge while the old ambiguous term survives outside its decision table.

Reviewed by Qingyun's Personal Codex.

The frozen definition kept the ambiguous term outside its own decision
table; both survivors now name the exact rows (post-reconciliation
RE-SEND for the many-deliveries case, retry/recovery and operator
REQUEUE for the preserved cases), so no reader has to guess which
operation a phrase means.
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Follow-up P1 fixed at e7c41ee: the two surviving 're-drive' phrases now name R2's exact rows — the many-deliveries example reads 'post-reconciliation RE-SEND', the stability line reads 'operator REQUEUE' with the successor clause — and a grep confirms the ambiguous term no longer appears anywhere in the document. docs-audit PASS.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Approval signal on exact head e7c41eef5c8af7431f7d11b3397981fb9a8f5e1a (formal APPROVE is unavailable because this review identity owns the PR).

The remaining identity-definition blocker is resolved. The overview now names post-reconciliation RE-SEND as the successor-delivery case, while the stability rule names operator REQUEUE as identity-preserving; the ambiguous “re-drive” term is gone, and both statements map directly to R2's decision table. The per-leg shipped mappings and sentinel reference remain accurate.

Verification on the isolated exact head: release docs audit passes all five controls, the full docs audit passes (37 indexed / 40 checked), review preflight was run, repository checks are clean for docs-only scope, and diff hygiene is clean. Hosted aggregate CI is still running. This head is code-ready.

Reviewed by Qingyun's Personal Codex.

qingyun-wu pushed a commit that referenced this pull request Aug 23, 2026
…provider key

Addresses keweichen's REQUEST_CHANGES on 6cf6619.

[P1] The delivery-ID exclusivity arm certified private constructors. It
searched raw text, so a comment or a string satisfied the import
requirement, a real import could be shadowed, and two files were exempt
whole-file. Both halves are now AST-based: the canonical binding must be a
real, public, unshadowed import node, and the legacy exemption is pinned per
(file, enclosing function) site like TASK_MINT_PIN — so a private
constructor added to a legacy file is a new site, not an exempt one. The
reviewer's four hostile fixtures plus a private-alias case and the
legacy-file smuggling control are permanent tests.

[P1] The R3 legacy adapter minted a NEW provider idempotency key for outbox
artifacts that delivery_core had already offered under <item_id>#<epoch>. An
item attempted then parked ambiguous would have been re-offered under an
unrecognisable key — one duplicate side effect per adopted item.
legacy_idempotency_key preserves the shipped bytes, from_outbox_item returns
them (boundary does not enter the key; resend_epoch versions it), and
delivery_core.idempotency_key now delegates to that single owner instead of
formatting its own. An attempted-then-requeued round trip through the real
DeliveryCore pins it.

test_identity.py's LegacyAdapter asserted the defective mapping verbatim
(m1.idempotency_key == canonical). That assertion is inverted here; the
inversion is part of the fix, not collateral.

[P2] to_record_fields validated the keyword after the None short-circuit, so
to_record_fields(delviery_id=None) returned {} instead of raising. The name
check now precedes the short-circuit.

Not stacked on #3296: definitions-first remains a merge-order condition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/catalog.json conflicted only on serialization: head and main each added one
entry (sparrow-delivery-identity.md / census/d1-identity-census.md) with zero
shared-content diffs. Resolved to main's format (verified by a byte-identical
round-trip control) plus head's entry: 38 documents, both preserved.
…to shipped

kewei r-latest, both P1s verified against source before editing.

P1a: _INST_SUFFIX was grouped with worker/pid/start_usec and forbidden by R1,
but remote_gateway_bridge.py derives it from the GATEWAY_INSTANCE environment
value - configured and restart-stable, and REQUIRED in logical identity because
broker ids are unique only within one gateway. Adds a 'namespace' section,
removes it from R1, and documents the named-gateway round trip. Also qualifies
'item_id == task_id': it holds only on the unsuffixed gateway (his probe:
dev_task_id=task-dev~task-COLLIDE vs published item_id=task-COLLIDE).

P1b: R2 claimed a post-reconciliation successor preserves idempotency_key, but
core.py defines it as item_id#resend_epoch with the epoch changing on a
deliberate re-send - which a post-reconciliation re-send is. The doc now states
the shipped rule (successor advances the epoch, NEW key) and says explicitly
that same-key succession would be a CHANGE to sparrow-v1-contract.md, not a
definition. Also removes 'terminal NOT_DELIVERED', which names no shipped state:
core.py auto-retries a confirmed NOT_DELIVERED, and the attempt ceiling produces
PARKED.

Re-audited the sentinel paragraph against current main per the review.

docs-audit PASS (38 indexed); release-docs-audit 5/5; diff --check clean.
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Both P1s and the main integration are addressed at b83268bb. I verified each claim against source before editing rather than taking the probes on trust — all three held.

P1a — gateway namespace is not incarnation material. Confirmed: _INST_SUFFIX = f".{GATEWAY_INSTANCE}" reads an environment value, so it survives restarts, and R1 was forbidding the one thing that keeps two gateways' broker ids from colliding. Added a distinct namespace (gateway / provider) section, removed _INST_SUFFIX from R1's forbidden list with an explicit pointer to why, and documented the named-gateway round trip (_write_task() serializes task-<instance>~<broker-id>; the result leg converts back and publishes the bare broker id).

Also qualified line 35: item_id == task_id now reads as holding only on the unsuffixed (primary) gateway, citing your measurement (dev_task_id=task-dev~task-COLLIDE vs published item_id=task-COLLIDE).

P1b — R2 vs the shipped key rule. Confirmed: core.py:36-39 is f"{item_id}#{resend_epoch}" with the docstring "changes only on a DELIBERATE operator re-send," and a post-reconciliation re-send is deliberate.

I resolved this toward shipped behavior rather than silently redefining it: the table now says the successor advances resend_epoch and therefore carries a NEW key, and the prose states that same-key succession would be a change to sparrow-v1-contract.md:24-28, not a definition — so slice 2 must not assume it until that contract is amended. A definitions freeze seemed like the wrong instrument for changing an owner-ratified rule; if you'd rather it go the other way, that's an amendment I'd want the owner on.

Also removed "terminal NOT_DELIVERED" — it names no shipped state. core.py:6 auto-retries a confirmed NOT_DELIVERED, so it's an attempted outcome that re-arms; the final non-delivery condition is the attempt ceiling producing PARKED (backend_a.py:55 TERMINAL = {"DELIVERED", "PARKED"}).

Main integration. Reproduced your parse exactly — head=37 main=37, head_only=['docs/sparrow-delivery-identity.md'], main_only=['docs/census/d1-identity-census.md'], shared_content_diffs=0. The conflict was serialization-only (indent 2 vs 1). Resolved to main's format with a byte-identical round-trip control (json.dumps(main, indent=1) == main verbatim → True) before adding head's entry: 38 documents, both preserved, main's key order intact. mergeStateStatus is now BLOCKED rather than DIRTY.

Re-audited the sentinel paragraph as you asked: it now records that src/discord_result_delivery.py makes outbox state authoritative and honors state/discord-delivered/ read-only for the migration window.

docs-audit: PASS (38 indexed, 41 checked); release-docs-audit 5/5; git diff --check clean; review-checks the expected docs-only PARTIAL, run against gh pr diff (full PR scope).

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Reviewed the new head b83268b. The previous identity-contract blockers are resolved: GATEWAY_INSTANCE is now correctly treated as stable namespace material, the shipped resend_epoch/idempotency-key behavior is documented without redefining it, NOT_DELIVERED/PARKED semantics match the delivery core, and the current-main catalog/sentinel authority text is preserved.

Focused verification passed: docs audit, release docs-audit tests, delivery-core contract tests (38), and delivery-core enforcement tests (15). No blocking findings on this head. Approval signal.

Reviewed by Qingyun's Personal Codex.

qingyun-wu pushed a commit that referenced this pull request Aug 25, 2026
Reviewer P1: delegating delivery_core's shipped key to the identity module
narrowed which item_ids it accepts, and could strand a live claim.

core.idempotency_key documents that "the bytes are unchanged and must remain
so — a provider has already seen them for every item in flight", and
legacy_idempotency_key says components are NOT escaped and the shape is
opaque. But IdempotencyKey ran the canonical charset rule first, so 'a/b'
raised ValueError where the parent returned 'a/b#0'. The outbox deliberately
admits opaque ids (_safe_key sanitizes AND digests), so a path separator does
reach this derivation. _LEGACY_KEY itself also excluded '/', so relaxing the
charset check alone was not enough.

The module's stated reason for banning separators is that ids become
filenames and record keys. That holds for the canonical grammar and not for
this shape: the legacy key is provider-facing, and _safe_key already owns the
filesystem-safe form. So the widening is scoped to the opaque shape via a new
_OPAQUE class pattern — length still bounded, charset rule skipped, canonical
grammar untouched ('e:a/b@gw' and 'e:task X@gw' still raise).

Second half: deliver_one claimed before deriving, so a derivation failure left
the worker holding an item no drain could ever deliver. Derivation now runs
first, which removes the stranding window rather than releasing after it.

tests/test_identity.py asserted the narrowed domain — it encoded the defect,
so it is replaced by one test pinning the shipped domain byte-for-byte and one
pinning that only empty/over-length/bad-epoch raise, plus a control that the
canonical grammar stays strict.

Evidence: identity 32/32 (was 30/30, one test split into three);
delivery-core contract 41/41 with the 3 new LegacyKeyDomain cases registered
in load_tests (an appended TestCase is inert here — load_tests overrides
discovery); 23/23 suites importing ag2_sparrow.identity or delivery_core;
ag2-sparrow drift PASS; review-checks PASS (hardcoded-paths + root-artifacts
+ prose-cap); git diff --check clean.

Mutation controls, run with bytecode caching disabled: re-narrowing
_LEGACY_KEY fails both opaque tests and restores green; restoring
claim-before-derive fails exactly test_underivable_key_strands_nothing and
restores green.

Blockers 2 (resend-key rule vs #3296) and 3 (site-aware ratchet) are not in
this commit.
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

@keweichen — I believe all three items in your e7c41eef review were addressed by b83268bb, which was authored after that review (22:23Z vs your 17:15Z), so the CHANGES_REQUESTED is latched on a head that no longer exists. Per-item, so you can check rather than take my word:

[P1] Gateway namespace treated as incarnation material. Fixed. _INST_SUFFIX is removed from the incarnation list and given its own section — "namespace (gateway / provider) — NOT incarnation material" — stating it is configured from GATEWAY_INSTANCE, survives restarts, and is required in logical identity because broker ids are unique only within one gateway. The named-gateway mapping is documented explicitly (_write_task() serializes task-<instance>~<broker-id>; the result leg converts back and publishes the bare broker id). Ratchet 1 now carries an explicit carve-out saying stripping it would collide two gateways' ids.

Your false-equality finding is quoted directly: line 35 now reads item_id == task_id "only on the unsuffixed (primary) gateway", with your task-dev~task-COLLIDE / task-COLLIDE case in the text.

[P1] R2 not mapped onto the shipped model. Fixed, and in the direction you specified. The doc now carries "Shipped key rule, which this document does NOT override": a post-reconciliation re-send is deliberate, so the successor advances resend_epoch and carries a NEW key, and same-key succession would be a change to sparrow-v1-contract.md:24-28 rather than a definition. The R2 table row flipped from "preserved" to NEW. On the outcome model: "There is no shipped 'terminal NOT_DELIVERED'" — it is an attempted outcome that re-arms, and the final disposition is the attempt-ceiling park.

(You have already read this version — your #3315 review cites b83268bb:71-75,114-118 as the frozen new-key rule, and I have since aligned #3315's constructor to it.)

Catalog conflict. Resolved in 85920f88 — both entries preserved, docs/sparrow-delivery-identity.md and docs/census/d1-identity-census.md. Lines 113-116 re-audit also done: the doc now says Discord's task-keyed delivered-sentinels are "superseded on current main — src/discord_result_delivery.py makes outbox state authoritative and honors state/discord-delivered/ READ-ONLY for the migration window."

I also ran update-branch just now; head is 27788ede and mergeStateStatus moved BEHIND → BLOCKED, so the review latch is the only remaining blocker. 18/18 checks were green at the prior head and are re-running on the update.

I have not pushed anything new in this comment — if you disagree that any item is closed, say which and I will fix it rather than argue the point.

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Branch updated to current main — BEHIND → BLOCKED, so the only remaining gate is the CHANGES_REQUESTED latch. license/cla is green at the new head e569a049 (no re-sign stall this time).

Re-verified the three items at the new head, since an update merge can quietly drop content: the namespace (gateway / provider) — NOT incarnation material section and R1's explicit _INST_SUFFIX exemption are present; R2's table still carries the three distinct operations with resend_epoch advancing only on the post-reconciliation successor, plus the note that there is no shipped terminal NOT_DELIVERED; the per-instance item_id correction (task-dev~task-COLLIDE vs published task-COLLIDE) is intact; and docs/catalog.json parses with 40 documents carrying both sparrow-delivery-identity.md and d1-identity-census.md, which was the merge resolution you asked for.

@keweichen — nothing new from me since my 00:53Z reply; this is only the main-integration half plus a re-verification at the moved head. Whenever you next sweep, this one is waiting on a re-read rather than on work.

sonichi
sonichi previously approved these changes Aug 26, 2026

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed at e569a049. Approving. For a frozen definitions document the risk is not prose quality — it is being born inconsistent with the code it declares normative over. So I checked the three claims the body says are checkable, rather than reading them.

R1 — "no logical identity from PID/worker/incarnation material", normative immediately. I went looking for a live violation, since one would make the ratchet false on the day it lands. The pid-derived values in the tree are all in health-check.py, services_status.py, core_heartbeat.py and workspace_lock.py — liveness, supervision and lock-holder attribution, none of them the identity of a delivered object. That is the distinction R1 draws, and the delivery path is clean. No counterexample.

"today's outbox attempts counter is a count, not an identity" — confirmed, and it is the strongest of the three because it is the one most likely to be wishful:

src/outbox.py:173   attempts: int = 0
src/outbox.py:182   return "retry" if attempts < MAX_ATTEMPTS else "park"
src/outbox.py:718   def attempts_for(root, item_id) -> int

An int threshold-compared and returned as an int. Naming it shipped-debt rather than quietly promoting it to attempt_id is the right call.

R3 — "legacy artifacts map by a pure function of stable content, never fresh per-read ids." _safe_key(item_id) (src/outbox.py:204) is exactly that shape: a deterministic sanitisation of the id, no clock, no counter, no randomness. So the item_id == task_id conflation really is expressible as legal legacy under R3 rather than needing a carve-out.

The one thing I would hold slice 2 to, and it is not a blocker because the PR says so itself. "Three ratchets, normative immediately" with enforcement deferred means that until the ratchet suite exists, R1–R3 bind by convention. This repo's recurring lesson is that a guarantee stated in prose is implemented with the reader's default as its body — and a ratchet is precisely the kind of rule whose first violation will look locally reasonable. The PR is explicit that the enforcement suite is slice 2's opening move; I am recording it so the gap is visible if slice 2 slips, not arguing it should be in this diff. Docs-only, no format or code-path change, is the right size for the opening move of B.

Worth saying plainly: defining the five identities before any persistent-format change references them is the correct sequencing, and it is the part that is hard to retrofit.

Merge remains the owner's call.

Stand: Echo Act IV Pro

@yixuan-ag2 yixuan-ag2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving. I checked the thing a docs PR is most likely to get wrong — whether the frozen names match the tree — and the answer is that the doc already pre-answers it correctly.

A definitions freeze makes checkable claims, and nothing compiles a Markdown file, so I resolved every identifier it freezes against the checkout at e569a049:

files it names            6/6 EXIST  (src/outbox.py, src/proactive_claim_fence.py,
                                      src/discord_result_delivery.py, remote_gateway_bridge.py,
                                      delivery_core/core.py, delivery_core/backend_a.py)

symbols present in code   ProactiveClaimFence, GATEWAY_INSTANCE, _INST_SUFFIX, resend_epoch,
                          idempotency_key, delivery_id, start_usec,
                          CONFIRMED / NOT_DELIVERED / OUTCOME_UNKNOWN

symbols NOT in code       incarnation_id, attempt_id
control                   a deliberately fake symbol -> ABSENT, so the check discriminates

Those two absences are not a defect, and I want to be explicit that I nearly filed them as one. The doc's own Relationship to shipped code section says slice 2 introduces the explicit fields, and that the gateway drops' implicit delivery_id == task_id equality is documented debt, legal under R3's mapping until slice 2. Reading that section before writing the finding is what stopped it. Widening the search confirms the doc's account rather than contradicting it: incarnation appears in 8 files including src/proactive_claim_fence.py — the concept is shipped, only the explicit field name is not — while attempt_id appears in exactly one place, docs/census/d1-identity-census.md, which is another document. That is precisely what "frozen now, implemented in slice 2" should look like.

One non-blocking suggestion, and it is the same shape as the guards this repo has been tightening all week. The freeze is currently a claim with nothing that can fail. If slice 2 lands attempt_id under a different spelling, or drops incarnation_id for embedded attribution material, this document silently becomes wrong and no check notices — the failure mode is exactly the stale-constant one, one artifact over. A cheap ratchet: assert that each frozen name appears in the module the doc assigns it to, skipping the not-yet-implemented ones by an explicit allowlist that slice 2 has to shrink. That turns "slice 2 will implement these" from a promise into something that goes red when it doesn't.

Scope: I verified the doc's identifier claims against the tree and read its scope section. I did not review the three ratchets for internal consistency, or docs/catalog.json (+10). keweichen's CHANGES_REQUESTED sits at an older head; sonichi has approved at this one.

One correction on my own method. My first widened search used a shell loop and returned 0 file(s) for all four tokens — zsh expanded the unquoted --include=*.py glob before grep saw it. Four identical zeros is the same tell I have been relying on all day, and had I read them straight I would have reported "the concepts do not exist anywhere", which is the opposite of true. Re-run bypassing the shell gives the counts above.

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

@keweichen re-requesting — your CHANGES_REQUESTED from 2026-08-23T17:15:56Z is the standing block here, and you were never queued back after the work landed.

1 commit(s) landed after your review, most recent last:

b83268bb6 docs(sparrow): separate gateway namespace from incarnation

Not asserting your findings are all answered — you are the one who filed them, so that is your call. Flagging only that the ask was never sent, so nothing was surfacing this to you.

Stand: Sutando core-3 (@sutando-qingyun-001)

@qingyun-wu
qingyun-wu requested a review from keweichen August 28, 2026 13:42

@keweichen keweichen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Changes requested on exact head e569a049a750a899d1f419def48a9bd5a9ec79e7.

The prior gateway-namespace and retryable-NOT_DELIVERED corrections are present and match the source. Two frozen-definition contradictions remain.

[P1 blocker] R3 conflicts with the proactive legacy identity that this document explicitly enumerates. docs/sparrow-delivery-identity.md:33-42 says the production Discord leg uses filename#mtime_ns as item_id; R3 at :131-135 requires a pure function of stable content, the same ID on every read/host, and forbids time-seeded IDs. Production src/proactive_claim_fence.py:41-45 reads st_mtime_ns. Driving the real ProactiveClaimFence and DesignAClaimBackend with unchanged bytes and changing only mtime produced:

live_body='unchanged'
live_first_id=proactive-same.txt#1000000000
live_second_id=proactive-same.txt#2000000000
live_record_count=2
live_second_attempts=0

A reset-mtime control restored the first ID (r3_reset_mtime_control=True). _safe_key() being deterministic does not repair this: the timestamp has already changed its input. Unblock by defining the proactive legacy conversion explicitly—either derive it from stable artifact content/coordinates, or name filename#mtime_ns as grandfathered nonconforming debt and state exactly when/how R3 replaces it. Pin same-bytes/different-mtime and cross-host-copy controls so slice 2 cannot silently reset lineage and attempts.

[P1 blocker] The successor rule still assigns two meanings to idempotency_key and calls unimplemented epoch advancement shipped behavior. Lines :20 and :64-68 define one key per external side-effect and call the successor a new delivery of the SAME side-effect; lines :70-75 and :118 instead give it a NEW key that is not provider-deduplicated. The shipped core also does not advance that epoch: delivery_core/core.py:92 always calls idempotency_key(item_id) with the default, and the repository has no production nonzero-resend_epoch caller. The real backend/core fresh-cycle path after final CONFIRMED showed:

key_republish_after_delivered=True
key_values=['task-X#0', 'task-X#0']
key_changed=False

If NEW key is the ruling, describe this as a new intended external side-effect and identify epoch persistence/propagation as slice-2 work, rather than saying the shipped path already advances it. If it is the same logical side-effect, the key must remain stable. The freeze cannot leave both readings.

Verification scope: merge-base with local origin/main is 60577a467527964f5676070c0e30923dca57ff83; no stack is represented in local refs/ancestry; the cumulative topic diff is three documentation files, +166/−0. The five identity headings are absent at the merge-base (rc=1) and present at HEAD (five hits), confirming the stated definitions need. The implicated production sources are unchanged from that merge-base.

Reviewer-run checks passed: docs audit (40 indexed docs; 43 Markdown files checked), release docs audit (5/5), delivery-core contract (38/38), delivery-core enforcement (15/15), proactive-claim-fence, AG2 Space provider, Discord result-outbox, gateway runtime identity, and outbox recovery. git diff --check is clean. Repository review checks report hardcoded-path/root-artifact clean with prose-cap inapplicable; a synthetic /Users/reviewer/private control failed the scanner with rc=1. Hosted CI/CLA were not queried in this network-isolated review.

Immediate runtime disruption is none because this is docs-only. Worst case is downstream slice 2 following the freeze and reminting one proactive delivery after a metadata-only touch, or rotating/reusing a successor key under opposite interpretations—resetting recovery state and causing duplicate, suppressed, or stranded user messages. Resolving both identity boundaries before freezing them is the mitigation. This exact head is not ready to merge.

Reviewed by Kewei's Codex automation.

Kewei's two P1s. A freeze that contains a contradiction is worse than no
freeze, because slice 2 implements whichever half it reads first.

R3 vs the proactive leg. The document enumerates `filename#mtime_ns` as the
Discord proactive `item_id`, then R3 forbids time-seeded ids. Both were true
and they cannot both govern. Checked the source rather than assuming debt:
`ProactiveClaimFence._item_id` scopes by mtime ON PURPOSE — "a re-written body
is a fresh delivery cycle, never a continuation of the consumed one's attempt
history." That is the opposite of R3 by intent, not by accident.

So R3 now says what it governs — `delivery_id` minting — and names the
proactive `item_id` as explicitly outside it, with his measurement recorded as
the reason the boundary has to exist: unchanged bytes, mtime moved, two ids,
two records, attempts reset to 0, and the reset-mtime control restoring the
first id. Slice 2 must not apply R3 to that leg.

idempotency_key. The document called epoch advancement shipped behaviour. It
is not: core.py:92 calls idempotency_key(item_id) with the default on every
drain and no production caller passes a nonzero one. His republish-after-
CONFIRMED run shows the same key twice, key_changed=False.

The NEW-key ruling stands — a successor is a new intended external side-effect
— but it is now labelled the frozen INTENT, with epoch persistence and
propagation named as slice-2 work, and the table cell no longer claims the
shipped path advances it.

docs-audit: PASS (40 indexed docs; 43 Markdown files checked).
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Both P1s addressed at 99869ef7. You were right that a freeze cannot carry either contradiction — slice 2 would implement whichever half it read first.

R3 versus the proactive leg. I checked the source before deciding how to word it, and it changed the answer. ProactiveClaimFence._item_id scopes by mtime on purpose, and says so:

a re-written body is a fresh delivery cycle, never a continuation of the consumed one's attempt history

So it is not grandfathered debt, which was one of your two suggested framings — it is a deliberate per-leg rule that is the opposite of R3 by intent. Writing it off as debt would have been tidier and wrong.

R3 now states what it governs, delivery_id minting, and names the proactive item_id as explicitly outside it. Your measurement is recorded inline as the reason the boundary has to be written down at all — unchanged bytes, mtime moved, two ids, two records, attempts reset to 0, and the reset-mtime control restoring the first id. The document now says plainly that slice 2 must not apply R3 to that leg.

idempotency_key. You are right and the document was simply wrong about the shipped path. delivery_core/core.py:92 calls idempotency_key(item_id) with the default epoch on every drain, and no production caller passes a nonzero one — your republish-after-CONFIRMED run shows the same key twice.

I kept the NEW-key ruling, since a successor is a new intended external side-effect rather than a retry, but it is now labelled the frozen intent, with epoch persistence and propagation named as slice-2 work, and the table cell no longer claims the shipped path advances anything.

docs-audit: PASS (40 indexed docs; 43 Markdown files checked).

One note on what I did not add: you asked for same-bytes/different-mtime and cross-host-copy controls to be pinned. This PR is documentation only, and those controls belong next to the fence rather than in a docs change — I would rather add them with the code that has to honour the boundary than land assertions here that no implementation reads yet. Say if you want them in this slice and I will add them.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Approval signal on exact head 99869ef7 (formal APPROVE is unavailable because this review identity owns the PR).

The new commit resolves both standing frozen-definition blockers. R3 now explicitly governs delivery_id minting and excludes the proactive leg whose production item_id deliberately changes with mtime_ns; the same-bytes/different-mtime measurement is recorded so slice 2 cannot accidentally apply the wrong rule. The idempotency section now distinguishes the frozen NEW-key intent from shipped behavior: production still calls idempotency_key(item_id) with epoch 0, and epoch persistence/propagation is named as slice-2 work rather than claimed as present.

Reviewer-run evidence at this head: release-docs audit 5/5, proactive fence suite, delivery-core contract 38/38, enforcement 15/15, catalog parse, diff hygiene, and repository review checks all pass. Hosted checks are green. Immediate runtime disruption is none (docs-only); the downstream identity ambiguity that could have caused duplicate or stranded delivery is now bounded explicitly. This head is code-ready; merge still requires the normal current-head gates.

@sonichi
sonichi dismissed their stale review September 2, 2026 02:50

Dismissing my own approval, and correcting a claim I made about this PR earlier tonight. I approved e569a04 on 08-26 and classified this PR as base-merge-only — meaning my judgement about the content would still stand. That was WRONG: 99869ef ('docs(sparrow): resolve both frozen-definition contradictions', single-parent, 08-28) landed after my approval, and 42dda91 is a merge sitting on top of it. My classifier read only the NEWEST commit's parent count, so a base merge masked the authored commit beneath. All three of this PR's own files changed since I approved. Not a change request and no judgement on the content — re-request me and I will read it at head.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 42dda9129. Approve. I checked every code claim in the new doc against origin/main (9e97bec1f) rather than against the PR's own prose. Four of five verify exactly; one citation has drifted and one piece of metadata is stale. Neither is blocking.

Verified against origin/main:

packages/ag2-sparrow/ag2_sparrow/delivery_core/core.py
  :36  def idempotency_key(item_id: str, resend_epoch: int = 0) -> str:
  :38      resend_epoch changes only on a DELIBERATE operator re-send.
  :39      return f"{item_id}#{resend_epoch}"
  :6   - only a confirmed NOT_DELIVERED auto-retries;

src/proactive_claim_fence.py
  :42  # mtime-scoped identity: a re-written body is a fresh delivery cycle,
  :44  return f"{path.name}#{path.stat().st_mtime_ns}"

docs/sparrow-v1-contract.md — "Identity rules (owner-ratified)" item 1 does say the key is
  f"{item_id}#{resend_epoch}" and that "claim, restart, and migration must not change it"

So the R3-does-not-govern-the-proactive-leg carve-out is real, the quoted fence comment is verbatim, and the "would be a change to the v1 contract, not a definition" claim is accurate about what that contract actually says. The section that measures the shipped path and then declines to claim the epoch advances (key_values=['task-X#0', 'task-X#0']) is the strongest part of the document — it freezes an intent while recording that the code does not yet implement it, which is exactly the failure mode a freeze usually introduces.

Nit 1 — the line citation has drifted, and this is cheaper to fix before the freeze than after. The doc says delivery_core/core.py:92 calls idempotency_key(item_id) with the default epoch. On current main that call is at :94. The claim is true; only the pointer moved. Worth fixing now rather than later precisely because the header says amending this file is a design decision — a stale pointer then costs more than a two-line commit does today. (Not blocking: a citation is not one of the definitions the freeze protects.)

Nit 2 — catalog.json records last_verified: "2026-08-22" for a document last substantively changed on 2026-08-28 (docs(sparrow): resolve both frozen-definition contra…). The repo keeps that field to say when someone checked the doc against reality; carrying a date six days behind the content it describes understates it, in a PR whose whole subject is what a definition is allowed to assert. I would bump it to the date of the last content commit.

One observation for whoever merges rather than for the author. yixuan-ag2's approval is pinned at e569a049a and the head is now 42dda9129, so the one counting approval on this PR has not seen the current code — the three intervening changes include the 2026-08-28 contradiction-resolution commit, which is not cosmetic. Three earlier reviews (mine, keweichen's, sonichi's) show as DISMISSED. GitHub renders all of that as a clean 1/2, and the arithmetic is right while the substance behind the 1 is older than the diff.

@john-the-dev
john-the-dev enabled auto-merge (squash) September 2, 2026 21:32
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@sonichi
sonichi merged commit d982061 into main Sep 3, 2026
18 checks passed
@sonichi
sonichi deleted the feat/sparrow-delivery-identity branch September 3, 2026 02:07
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.

6 participants