v0.9.0: sub-agent transcript capture, the 1:n lifecycle, the record gates and the attestation fix - #638
Merged
Conversation
These three records were captured in an earlier session and were left untracked on main, where nothing substantive is meant to land. They are committed here unchanged, on the branch, so they stop riding the working tree of whatever session touches this checkout next. Not this branch's subject: they concern the launch flow for a managed repo, a session URL that reached three commit messages and two pull requests, and the AI-assistance disclosure convention covering commits only. Assisted-by: Claude:claude-opus-5
The transcript store captures only the top-level session transcript. Every sub-agent transcript is missed, which on the measured corpus is roughly three quarters of all recorded bytes: 968 sub-agent files against 68 session files. The session-end hook reads the single transcript path the harness hands it and enumerates nothing, and no sub-agent event is registered. Records the finding, the intent that closes it, its spec, and the lineage decision the spec turns on. The intent covers three capabilities the product thinker specified together: capture sub-agent transcripts going forward, ingest the history already on disk per repository under that repository's own redaction configuration, and reconstruct any session as one agent-readable artefact with a telemetry file describing what the work cost. The ADR settles how lineage is carried. Explicit record fields, not the composite session id that 176 of the store's 267 records now carry, which cannot be reached from the identifier of the session that produced it and never recorded the agent type at all. Also captures a contradiction found while writing the spec: AGENTS.md says an ADR ordinal is hand-numbered and needs coordination between checkouts, while the decide verb mints a collision-proof timestamp id. The stale surface is the one an agent reads first, and it caused this ADR to be deferred on a risk that adr-45 had already removed. Assisted-by: Claude:claude-opus-5
A record could name only the session it belonged to, so a sub-agent transcript had nowhere to say which session spawned it or what kind of agent produced it. The only writable identifier was the session id, and hand-capture had been overloading it as <parent-prefix>--agent-<id>: 176 of the store's 267 records carry that shape, none of them reachable from the identifier of the session that produced them, none recording the agent type at all. Schema version 2 adds seven lineage fields and admits both versions on read. Capture takes a CaptureMeta rather than a positional list. The lineage scalars are framed into the raw text and run through the EXISTING two-stage redaction pass, then split back off, so no field reaches a record without passing the scanner; a frame that does not survive intact refuses the write. Supersession makes (session_id, agent_id) the unit. An agent can stop more than once, and the second stop carries a longer transcript with a different content hash, so a key on the hash alone writes a second record for one agent: 116 of 999 sub-agent transcripts on this machine show two or more stops, and the store already carries the same duplication on the main thread. A body whose stored counterpart is a byte-prefix of it now replaces that record; a shorter re-arrival is a no-op; divergent bodies stay side by side. spawn_attribution distinguishes two empties that would otherwise read alike: an agent spawned by the main thread, and an agent whose lineage is simply unknown. Capture requires it wherever an agent id is set. Migration of the existing composite records, the staging sidecar, the hook and the recovery verbs are later steps and are not in this change. Assisted-by: Claude:claude-opus-5
Registers SubagentStop and adds the hook behind it, which is the point the corpus starts accruing the three quarters of recorded work it has been missing. The hook stages and never captures, and always exits zero: the event is blocking, so a non-zero exit would stop the sub-agent from finishing. Staging carries a .stage.json sidecar rather than encoding lineage in the filename, which would repeat one directory earlier the defect adr-2609090636172016 removes. The sidecar is written before the .raw and torn down if that write fails, since a .raw without one reads down the legacy path, where a sub-agent's agent id would be parsed as a session id. Sidecar-less files still drain, so the raw transcripts already staged on this machine are not stranded. A sidecar that is present but unreadable is reported rather than falling back to the legacy reading, which would misattribute it. The drain takes main-thread entries first and bounds a pass by bytes as well as count. Staged transcripts are unredacted by design, and capturing sub-agents multiplies how many exist at once, so a pass that could only count entries would leave a growing pile of raw text behind. A sub-agent in an isolated worktree records that worktree as its working directory and the harness removes it when the agent stops, so resolving the repo the way session-end does fails exactly where the implementation lanes are. A session-to-store note gives the hook a second route, and refuses an ambiguous claim rather than guessing, because the wrong repo means the wrong scanner. Session capture changes too, deliberately and by decision: session-end now records its source path, so a session resumed with --continue is stored as one complete transcript rather than the truncated first half. This is a behaviour change to existing capture, adopted on the product thinker's call, not a side effect of the sub-agent work. Whether the event fires before the transcript is flushed is still unverified. The mitigations are in (a settle wait outside the staging lock, and a drain-time re-read that only ever extends a stored prefix) and both now count what they catch, so the measurement that gates the remaining steps is cheap to run. Assisted-by: Claude:claude-opus-5
Recorded rather than fixed in place, per the convention that a finding gets an armed record before it gets a patch. A staged raw transcript can live indefinitely (critical): staging holds unredacted text by design and its contract is that a file survives only until the next session starts, but the drain runs from the session-start hook of the repository the file belongs to. A repository nobody opens again keeps its raw transcripts forever. Four such files sit on this machine now, about thirteen megabytes, the oldest fourteen days old, and the per-repo status verb cannot see a pile in any other repository. Ingest would orphan sub-agents whose session resolves perfectly, because it asks each file's own working directory rather than the session's. That is the right authority for a main thread and the wrong one for a sub-agent, which records a worktree the harness has since removed. Summing reported token usage over-counts it: one response is written as several lines that each repeat the same usage, so a naive sum multiplies by the block count. Measured at 1.77x on one transcript and 5.28x across ten, a factor that varies per session and so cannot be corrected afterwards by a constant. The shared file lock's hundred-millisecond backoff ceiling admits roughly ten writers a second, so a burst past forty or fifty simultaneous completions exceeds the staging timeout and those stages are refused. Pre-existing in the lock helper, but unreachable until something started staging concurrently. Assisted-by: Claude:claude-opus-5
…akes a destination Two recovery verbs over the corpus that already exists. migrate repairs the records filed under a hand-made composite id. It recovers the untruncated parent session id from each record's own body, requires the recovery to begin with the stored prefix, and refuses rather than guessing. It reports by default and writes only under --apply; the content hash, the capture stamp and the filename are left alone, because dedup and every existing reader depend on them. The spec was wrong about the composite's shape and about what is recoverable. It gives one form, prefix--agent-id, but 71 of the 176 records carry a workflow segment in the middle, and splitting left to right refuses every one of them: splitting from the right is what reads both. It also records agent type and depth as never held anywhere, when the harness's per-agent metadata still exists for all 176, so the repair recovers the type, the depth and the parent rather than writing a record that says its own lineage is unknown. The workflow segment has no field to land in and is dropped, which the report names. ingest takes its destination as a required operand and refuses to derive one. A transcript must be redacted under its own repository's scanner configuration and never another's, so a destination inferred from the working directory is a privacy fault waiting to happen rather than a convenience. Ownership is resolved for the session before the file. A sub-agent in an isolated worktree records that worktree as its directory and the harness removes it when the agent stops, so asking the file alone orphans agents whose session places cleanly: on this machine 19 of one session's 24 sub-agents have a vanished directory and all 24 resolve through the session. Orphans are ignored and reported, never guessed, and are adopted only when the destination's own config names the project. Records gain adopted_project, so schema 3; parsing stays presence-based and reads 1, 2 and 3 alike. Assisted-by: Claude:claude-opus-5
…elemetry A session's transcripts are only useful as a set, and until now nothing assembled them. reconstruct takes a session and emits one self-contained Markdown artefact, readable without the store or the harness's files, plus a telemetry file describing what the work cost. Telemetry counts one usage per response, not per line. The harness writes a response as several content-block lines that each repeat the same usage object, so summing lines multiplies a response by its block count: on a real session of 55 records, 3711 usage-bearing lines resolve to 1801 responses, an inflation of 2.06x. The factor varies with how many blocks a response happens to have, so a naive total is not merely imprecise, it is wrong by an amount that changes between sessions, which defeats the comparison the file exists to support. The artefact keeps the main thread contiguous and appends each sub-agent, with spawn and join markers in the thread and a timeline table at the head. The spec asked for the sub-agent to be nested physically at its spawn point; that is wrong for asynchronous agents, and the test that proves it shows a delegate's conclusion rendering ahead of a main-thread turn that ran while it was still working. On real data one delegate spans 148 turns of its parent's thread. Nesting survives as data, not as indentation. Where the record is incomplete the artefact says so rather than reading as whole: a completeness block reports absent main threads, unattributed agents, unparseable lines, rejected duplicate records and elided blocks, and every placement records what placed it, so a consumer can weight a heuristic differently from a stored fact. Resolves: iss-2609090723027424 Assisted-by: Claude:claude-opus-5
…ture Two findings from the branch's security review, both verified by exploit before being fixed. The artefact reproduced text blocks raw while fencing every other block type, so a transcript could emit the document's own headings and markers byte for byte: a section for an agent that never ran, a turn nobody took, a JOIN marker asserting a result arrived. The artefact's declared reader is a model being handed the session as context, and its own guide told that reader a heading it did not recognise was somebody speaking, which is exactly backwards for a forged heading that matches a real shape. Anyone who got text into one transcript, from a fetched page to a hostile sub-agent, could put words in the record's mouth. Redaction has nothing to say about it: those bytes are content the store correctly kept. Text is now fenced like everything else, reusing the dynamic fence this file already trusts, so there is one escaping mechanism to be right about rather than a judgement per block type about which content is dangerous. A blockquote prefix was considered and rejected: prefixing a line that begins the JOIN marker reproduces the marker exactly, which is the forgery it was meant to close. The guide now states the rule that shipped. Separately, migrate framed the lineage scalars before validating them, the reverse of Capture's order, so a scalar carrying line breaks could re-aim the frame that redaction splits on and write chosen values into agent_type, spawn_tool_use_id, lineage_source and spawn_attribution, discarding the real ones and reporting success. Validation now precedes framing, and frameLineage itself refuses a scalar containing a line break, so the invariant the store claims at its boundary is held in core rather than by the front door alone. Also corrects a comment that was wrong about the harness: exit code 2 is the only blocking status, so the launcher's non-zero exit on a missing binary is a visible diagnostic rather than a stall, and silencing it would hide the fact that transcripts are not being captured. Two reviews independently derived a critical bug from that comment; the code was right and the comment was not. Assisted-by: Claude:claude-opus-5
…y do Staging holds unredacted transcript text on purpose, and its own comment claimed a staged file survives only until the next session starts. That was false: the drain runs from the session-start hook of the repository the file belongs to, so a repository nobody opens again keeps its raw transcripts forever. The store holds 14.2 MB across three repositories today, the oldest 26 days, and the per-repo status verb could not see any of it from here. Sub-agent capture, landed earlier on this branch, turns one staged file per session into one per sub-agent completion and stages during a session rather than after it, so this had to be closed before that ships rather than after. A drain now runs while a session is live, from the prompt hook, with a budget small enough not to delay a prompt, and ahead of the rules work so an unparseable rules file cannot switch redaction off. A backlog survey walks every repository in the store and reports counts and sizes only, never another repository's identifiers, since that text renders inside a different repository's session. An age limit buys priority and reporting, not removal. The issue asked for a staged file to be redacted or deleted at the limit; neither is available. Redaction is what the drain already does, and a file is old precisely because nothing ran, not because redaction was tried and failed. Deleting the only copy is the thing staging exists to prevent. So an overdue entry sorts to the front of every drain and is named in the notices, and nothing removes it. A deterministic redaction refusal is now separated from a retryable failure and quarantined with its reason, rather than re-read and re-refused by every later drain while filed as awaiting redaction. This changes state, not exposure: those bytes stay on disk until a human discards them through the one verb that deletes. Resolves: iss-2609090722466403 Assisted-by: Claude:claude-opus-5
…ntents The work was filed as one intent covering three capabilities. A conceptual review argued they were three separable deliveries, and the landing order had already delivered them as three independent steps that each left the tree green, which is the tell. The record now says what the code did. Capture keeps the original intent and its six criteria: a sub-agent's transcript is stored through the same redact-on-write path as its main thread, at any depth, fail-closed, idempotent, and reporting a miss rather than swallowing it. Recovery takes the three about history already on disk, brought into the repository that owns it under that repository's own redaction configuration. Reconstruction takes the three about the artefact and its telemetry. Capture's scope conditions now say plainly what is not known: whether the completion event fires before the harness has flushed the transcript is UNVERIFIED. The mitigations shipped and count their own effect, but the residual rate has not been measured over a corpus, and an intent that claimed otherwise would be claiming a measurement nobody has taken. Applied to this machine rather than only tested: 176 records repaired out of their composite identifiers, 1066 transcripts ingested, the store grown from 267 records to 1104. Thirteen transcripts were refused by fail-closed redaction over network addresses it could not redact, which is the guard working rather than a shortfall. Each close left a fidelity review owed; those receipts stand open. Resolves: iss-2609081917287384 Assisted-by: Claude:claude-opus-5
Closing each spec reconciled its intent from planned to shipped, and the spec bodies still linked to the planned folder, so eight links resolved to nothing and the record gate refused. The close reported success and left the tree failing its own lint, which is captured separately. Assisted-by: Claude:claude-opus-5
An abcd worktree held three untracked records and nothing else: an intent draft about setting up a managed repository's release-rendered site, and two captures, one about the plugin-provisioned binary lacking a verb its own surface documents and one about the capture-time scanner over-redacting an ordinary word. Being untracked, they were in no commit and on no branch, so a worktree prune would have taken them with it, and 26 worktrees were pruned from this checkout today. Ownership was established before they were touched rather than assumed: both live sessions in the repository the worktree was named for were asked, and both disclaimed the records independently, each observing that all three are about abcd itself. Nobody claims them and they belong here, so they are committed here. The draft joins the brief's later-phase index, which is gated on the drafts folder and refuses a draft it does not list. Assisted-by: Claude:claude-opus-5
The earlier anti-forgery work fenced the artefact's block CONTENT and left its block METADATA raw, so the class it set out to close stayed open. A tool call's name, its identifier, a tool result's identifier and a turn's model name were formatted straight into the document outside any fence, with no guard but a helper that substitutes a dash for the empty string. A line break in any of them emitted lines of the document itself, and the shapes it could emit were an agent section heading, a turn heading and a join marker: exactly what the earlier fix was meant to make unforgeable. Its regression test planted the payload only in a text block, which is why this passed. The sweep found nine sites, not the four the finding named. Two more are transcript-controlled: an unknown block's type, and the tool-call identifier the spawn point is recovered from when placement came from the transcript rather than the record. The rest are record-derived, where a line break cannot survive the frontmatter parser but a backtick or a pipe can, and a pipe in the timeline shifts every later cell so the table reports spans against the wrong agents, which is a false claim rather than a cosmetic one. Sites that were already safe were routed through the same cleaner anyway. A per-site judgement about which values are dangerous is precisely what let this gap open, and the fix that keeps a judgement keeps the gap. The cleaner is the repository's existing canonical one-line cleaner rather than a fourth copy of an escaping rule; the surface layer's own sanitiser does not fit, because core cannot import it and its semantics are to drop the whole value, which is right for a record write and destructive for a label. The guide the artefact hands its reader stated a rule the renderer did not hold. It now states the one that ships, and it no longer asks the reader to classify anything: every line of this document begins with words the document chose, and nothing quoted from the session can begin one. Resolves: iss-2609091913570877 Assisted-by: Claude:claude-opus-5
Three agent sessions worked autonomously for a day in a managed repository, one of them filing, planning and implementing an intent end to end without the human interview. Their run was treated as a field experiment on abcd's gaps: what does an autonomous operator need that abcd does not give it. This is what they found, recorded here so it can be assessed rather than lost with their sessions. Thirteen of these had already been written as properly formed records into a handoff directory in that repository, over two days, and none had ever reached this ledger. The channel exists and nothing drains it, which is itself the first finding about the arrangement. The synthesis is the most valuable single item and has its own record: the record verbs are sound, and the failures were all at the edges, where the tool knows something and does not say it. It holds the enum it is validating against and names only the offending value; it had the closing commit in hand when it minted a receipt and did not write it down; it names the file it wants without saying how to make one. That is a testable claim about where to look, not a complaint, and it earns a record rather than a footnote. Two findings were corroborated by more than one session independently, which is evidence about likelihood rather than possibility: an unknown sub-verb minting a draft intent from its own name, and required flags being learned from a refusal rather than from help. Two more were appended to existing records that had already predicted them rather than filed again. One record is positive and kept deliberately: every record verb worked from worktrees across 27 branches, and the ledger's one-file-per-record shape produced no merge conflict in the whole run. What held is evidence in an experiment, not filler. Assisted-by: Claude:claude-opus-5
…correction The session that hit it checked the filed record and corrected the weight rather than the content: once the union attribute was in place the decisions log stopped conflicting entirely, while the changelog went on conflicting on most of the merges that remained, because union is the wrong remedy for it rather than an unapplied one. Two conflict classes that look identical at a glance have different fixes, and a remedy scaffolding only the attribute would close one and leave the other untouched. Assisted-by: Claude:claude-opus-5
…corrected The session that filed, planned and implemented an intent end to end without the human interview reported nine items at the end of its run. Three were already in the ledger from the earlier sweep and are left as they are. Five are new. One is recorded against what was actually measured rather than what was reported. The corrected one matters most for how these reports are read. The session believed two captures had been silently lost to an exit status of zero. Tested on both the published release and current source, an unknown category is refused with exit status one, the error goes to standard error as JSON, standard output is empty, and no record is written: verified against the ledger and a clean tree. Nothing was lost. What the session saw was a machine-readable invocation producing no machine-readable output, in a pipeline that did not surface the exit status, for a flag whose accepted values are documented nowhere. Those three together read as silent loss, and the report was made in good faith. The finding is recorded as what it is, a shape that makes an operator doubt the store rather than the flag. The sharpest new one came from checking the session's own claim against this repository. It reported writing two invented record ids into a spec and catching them only on re-reading. Nothing gates that. Attempting the check here showed why it is harder than it looks: the decisions family carries two filename conventions, one zero-padded ordinal that differs from the id in its own frontmatter and one timestamp without the prefix the id begins with, so no cited id can be mapped to a file by name at all. Any check has to resolve through frontmatter, which is also why it does not exist yet. Also recorded: an intent can be planned with no impact and only discovers it at spec close, in the landing commit, which two sessions hit independently in a day and both fixed by editing frontmatter because no verb stamps it; the grounds requirement is documented after the step that enforces it; and cross-session claim on worktrees and records is coordinated entirely by conversation, measured at four messages and about fifteen minutes for one session to join work already in progress. Assisted-by: Claude:claude-opus-5
…n three A reporting session corrected the account, and the correction makes the finding both fairer and worse. The auditors did not invent values at random: each computed a defensible hash by a stated convention and disclosed which one it had used, asking the host to substitute its own. The defect is that ingest accepts provenance the host never issued and cannot tell a conventional self-computed value from an arbitrary one, so the attestation attests only that some agent chose something. This repository has the same condition and acquired it knowingly. Three verdicts were ingested here today, each carrying hashes its auditor had computed itself and said so, and the ingest went ahead on the reasoning that the validator would object if the values were wrong. The validator checks the shape of a hash and never its value, so it objected to nothing. Three permanent Audit Notes here carry self-issued provenance, and a managed repository holds three more verdicts uningested for the same reason, its handover recording the condition so whoever ingests them does it with open eyes. Six verdicts across two repositories is enough to call this the normal outcome of the verb rather than an incident. Assisted-by: Claude:claude-opus-5
…'s mechanism The session supplied what its pipeline actually did: it merged standard error into standard output and parsed the result as JSON, so the exit status was never read. The refusal reached them, as a well-formed JSON object. That is a better finding than the one first recorded. On a merged stream a refusal and a success are both valid JSON, and telling them apart needs either the exit status the merge discarded or foreknowledge that an error object carries an error key and a success object does not. Merging streams is an ordinary thing for a consumer to do, and the machine-readable mode hands it two shapes that parse identically and mean opposite things. The record now also states plainly, for any later reader, that nothing was lost: the captures were refused, no record was written, and the tree was clean, checked on both the published release and current source. Assisted-by: Claude:claude-opus-5
The third session's run finished and sent two more, which complete the set. A verb reveals its requirements one refusal at a time, so resolving one issue took three calls. Reproduced here: the argument-count refusal names none of the required flags, and the grounds refusal arrives alone even when the other required flag is also missing. Each refusal in isolation is well written, and the grounds one explains itself and confirms nothing was written. The defect is the sequence, and it falls hardest on an autonomous caller, which pays a round trip per requirement every time because it carries no memory of the last session's discoveries. The other is a positive result, and it is recorded because a stress that finds nothing is evidence about the tool that is only legible if someone writes it down. Two adversarial reviews over an autonomously filed, planned and implemented intent produced eleven findings about the repository under review and none about abcd. Applying them needed no change to any record shape: a scope condition was reworded under its existing stamp, one criterion was added, readiness stayed green, and closing the spec moved both records in one step. That is the second positive finding from this experiment, and the two of them bound the negatives. The shapes that carry a claim and the verbs that move a record between states held under a workload nobody designed them for, a review loop with no human in it, across 27 branches from separate worktrees, with no conflict in the ledger. What failed was at the edges. Assisted-by: Claude:claude-opus-5
Upstream relocated the transcript store while this branch extended it, so the integration is not mechanical. Upstream's location discipline wins outright: the store is user-level, self-creating and keyed on the root-commit SHA behind a single Resolve seam, where this branch had assumed a directory that `ahoy install` created and `ownedDirsReal` refused when absent. Every place this branch reached for the old shape now goes through Resolve, and the notice it printed about a missing store is deleted, because the store cannot be missing. Our features are ported onto that seam rather than against it: lineage fields and their validators, supersession on (session_id, agent_id), the staging sidecar, the live drain, the staged age limit, quarantine, the cross-repository backlog survey, and the ingest, migrate, reconstruct, drain and discard verbs. Four things changed in the port and are worth naming. Upstream's ahoy package now imports history, so this branch's use of ahoy's detector inside ingest became an import cycle and is replaced by the git primitive underneath it. The SubagentStop hook shim is rewritten byte-for-byte against the hardened SessionEnd shim, since ours predated upstream's PATH-ownership check and would have run an unrecorded binary. Two tests pinning the install precondition are deleted, because upstream removed the precondition. Two more that provoked a failure by removing the records directory are rewritten to seal it instead, since the store now recreates what they deleted. The two migrations are different things and must not be conflated later: upstream's moves a corpus between locations, ours repairs composite session ids inside records. Assisted-by: Claude:claude-opus-5
Nine findings closed, all of them recorded before they were fixed and each with a detector watched failing first. They are committed together rather than split four ways because the generated surface files carry additions from several of them at once and cannot be separated; the records say which change answers which. Redaction no longer corrupts a reverse-DNS identifier whose first component is the account name. The detector suppresses a bare-username match only where it is an entire component of a dotted run of three or more, so a bundle identifier and a module path survive while a bare mention, a two-part filename and an email local part still fire. Before the fix the identifier was masked in the record's body and in its filename; the residue, that masking still rewrites by whole string rather than by byte span, is recorded separately because reversing that is a design decision in the one sanitiser every write path shares. The refusal that protects a write from a degraded scanner is now asserted, on seven paths rather than the three named: the sweep listed every unavailability site, and the decision record, the intent redactor and the lifeboat pack were unarmed beyond the finding's scope and are armed too. An unasserted guard on a fail-closed path is indistinguishable from an absent one. Ingest now verifies that its destination's repository root and its store key name the same repository, and fails closed when the root's own root commit will not resolve. The seam exists so that a transcript is never redacted under another repository's configuration, and it was trusting its caller to supply a pair it never checked. A session's sub-agents are reachable from the session identifier: `history list --session` is the set verb's front door, the human render carries the agent and its type, and `show` points at the set. The plugin page documents the flag and the lineage fields, which is what the JSON-only route lacked. A machine-readable refusal is now on standard output and says what it is, because a consumer that merges the streams could not tell a refusal from a success without the exit status the merge discarded. All three closed enumerations now name their accepted set when they refuse a value, rather than naming only the value. The findings gate can no longer be cleared by deleting the record it exists to protect. A deletion is a distinct refusal from an unfixed finding, because the remedy differs: the record must come back before it can be resolved, and a waiver is deliberately not consulted, since a deleted record carries no promise to re-ask. A record id sitting in two status folders now refuses every read of the ledger. Folder membership IS the status, so an id in two of them has no status at all. AGENTS.md no longer tells an author to coordinate an ADR mint that needs no coordination, and the correction is pinned by a test that mints an id and then refuses a router contradicting it, so the prose is held to behaviour rather than to another page of prose. Resolves: iss-2609100505142469 Resolves: iss-2609091915350221 Resolves: iss-2609091911060345 Resolves: iss-2609091915475296 Resolves: iss-2609100519128005 Resolves: iss-2609091143455568 Resolves: iss-2609100507430423 Resolves: iss-2609090636110810 Assisted-by: Claude:claude-opus-5
…it fixed The previous commit carried their Resolves trailers and did not move their records, so the resolution gate refused the range, correctly. The fixes are in f854118; these are the records catching up with them. One of the three is stamped breaking rather than fix. Making a machine-readable refusal self-describing on standard output also means two verbs that render and then fail now put two documents there where they put one on each stream before. A consumer that unmarshals the whole of standard output for those two verbs breaks. The refusal is always last and a test pins that, but the version number should carry the change rather than a footnote. Assisted-by: Claude:claude-opus-5
The release gate refuses a cut while a major or critical record captured since the anchor tag is still open, and the recorded deferral is the only way past. It is meant to be a judgement, so each of these carries its own reason rather than a shared formula, and each names why it wants a decision rather than a patch. They divide into four kinds. Some are capabilities wearing a defect's clothes: the release flow for a managed artefact that is not a plugin, an errata mechanism for a durable record, a surface that counts owed reviews, a substrate that records which session holds what. Some are choices between defensible bargains that the evidence does not settle: the lock's backoff ceiling, the typed relations the decomposition discipline names but the format cannot carry, what a hazard guard should cover beyond shell calls. Some would reverse a recorded decision: making identity masking span-based rather than whole-string reverses a contract every write path in abcd shares, in the fail-open direction, and the public banned-names list cannot be created on a fresh public repository because the visibility fence is a documented contract with three candidate reconciliations already recorded for a maintainer to pick between. And one is not a defect at all: the synthesis that the record verbs are sound and the failures are at the edges, where the tool knows something and does not say it, which is a lens over the others rather than a thing to fix. Two deferrals carry news. The session-URL finding is half closed in this cut, its CI half gated and its local half blocked on three product decisions about how a git hook resolves a binary it has no plugin root to find. The unredactable staged text from a deleted repository no longer sits in the store at all: the owner moved it out, with a note recording why nothing could ever process it, so what is deferred is the mechanism and not an exposure. Assisted-by: Claude:claude-opus-5
… walls Two findings where abcd refused on a state it had itself produced or could simply have read as absent. A link at the PATH target that resolves to nothing made install refuse forever. The repair existed and was unreachable, because the detector classified every non-owned link as a foreign occupant to be hand-resolved. The discriminator is now danglingness rather than provenance: a link resolving to a file is somebody's install and is still refused untouched, while a link resolving to nothing runs nothing and is cleared. abcd does not attempt to prove it wrote the link, because it cannot, and the write-through hazard is closed by removing the link rather than writing through it. An unconditional plugin-manifest read refused on any repository that declares no plugin. The record named one site; the refusal a user actually sees came from a second, so both are fixed, because fixing the named one alone would have left the changelog verb dying exactly as before. Absence now comes from the payload tree's own answer rather than from classifying a read error, so a present-but-unreadable or malformed manifest still refuses, and a manifest that was declared at the last release and has gone is still caught as a removal against the baseline rather than vanishing into an error that yields no verdict. Resolves: iss-2609100506256636 Resolves: iss-2609100506255436 Assisted-by: Claude:claude-opus-5
…rite it Three findings, and one repair of my own mistake. The previous commit swept two files of this work into a commit about dangling install links without the primitive they call, leaving the branch tip unable to build. That primitive is here, so the tip builds again; the broken commit stays in history rather than being rewritten, because it is local and the honest record of what happened. The record naming which binary the hooks may execute was honoured without anyone checking who could write it. Four read sites accepted a group-writable or foreign-owned record, the presence test followed a symlink, and in the proving test all five hooks executed an attacker-named binary. One primitive beside the existing guarded read now checks link, regular file, mode and owner before reading, and the two existing copies were migrated onto it rather than a third being written. The shims ask the same question with a single file test instead of parsing a listing, which avoids field-position, locale and access-control-suffix assumptions, catches the symlink shape, and fails closed when its tools are absent. This was NOT fixed by the previous release; it was believed to be. The fidelity-review request now carries provenance the host computed, and the ingest verifies it rather than checking its shape. The rubric hash covers the contract this binary enforces, written verbatim into the request so the auditor echoes the bytes that were hashed; the prompt hash covers the request minus its own provenance block, which the ingest recomputes. The measured scale was much worse than the record claimed: thirty-six ingested verdicts, thirteen distinct rubric values, and two digests appearing under both field names, so the field name carried no meaning across the corpus. Those thirty-six are deliberately left as they are, because editing committed audit notes would lay a second fabricated provenance over the first. Privacy hygiene no longer fires on the two shapes the conventions mandate, which is what made it unreadable at a hundred and fifty findings. One half of that removes a detection and says so in its record rather than hiding in a diff: the shared-root exemption now covers the subtree, so a third party's name directly beneath one yields nothing, where the committing user's own name there still hard-fails. Resolves: iss-2609091927085132 Resolves: iss-2609100505140261 Resolves: iss-2609100505145554 Assisted-by: Claude:claude-opus-5
…e grew A window declares what an entry was calibrated for, and three of them no longer fit. The cause is this cycle's own work: roughly fifty new records from a three-session field experiment on abcd's gaps, plus the intents, specs and decision record that carried the sub-agent capture work, all of which the widening, entailment and detection positions read over. Each is re-measured at this commit and moved to the smallest ten-thousand boundary leaving at least one per cent headroom, which is the rule the entries already state. Nothing about what a position reads has changed, so the kinds are untouched and only the declarations move. This is the honest half of a gate that could have been silenced instead. The alternative the failure offers is to narrow an entry's kinds, which would have made the number fit by reading less, and none of these entries is reading anything it should not. Assisted-by: Claude:claude-opus-5
Four findings deferred at the v0.9.0 cut were deferred because they were capabilities wearing a defect's clothes: the release flow for a managed artefact that is not a plugin, errata as a fourth terminal disposition on a durable record, a count of owed fidelity reviews on the bare status surfaces, and a claim record for which session holds which worktree. The product thinker confirmed all four as intents. Each is promoted from its issue so the back-edges are stamped, and the brief's later-phase index carries the four. The routing is graded into the calibration note. It is the first run where the routing question was asked of a class rather than a proposal, and the class had been drawn by the reason for deferral; that classification had already done the decomposition's work. Assisted-by: Claude:claude-fable-5-1
…ry copy Identity masking rewrote by whole string: every occurrence of the matched text on a line was replaced, including the ones the detector had deliberately cleared — a component of a reverse-DNS bundle identifier, a collision inside a longer word, an occurrence inside a URL span. A line carrying one genuine mention and one lookalike had the lookalike corrupted too, so the record no longer held the technical content it existed to hold. The rewrite now masks exactly the byte spans the detector recorded. Spans are validated against the line before the secret seal and applied after it, which the seal's byte-length preservation makes safe; overlapping spans merge into one cluster masked with the widest member's placeholder; and the line is rebuilt from original bytes, so a placeholder of any length never shifts a later span. The cost is accepted with the decision and named in the code: an occurrence the detector clears on purpose — a login inside a URL, including a credentials URL's userinfo — is no longer masked by accident, and the second-stage re-scan does not flag it either, because it is the same detector. The caller's own home path is outside that residue, because the detector flags every occurrence by the anchor the literal sweep uses; history, memory and ideate additionally run that sweep after redaction. Each proof was watched failing first: the record's own measured case, the mid-word case, two genuine mentions on one line, offsets across several length-changing placeholders, the same behaviour for a forge login and a real name, the seal's length preservation, byte-offset columns under multi-byte runes, and the fail-open residue pinned so it is known rather than discovered. A review found the one path that could still reach the whole-string fallback from a real scan — an identity span overlapping a sealed secret — and it is closed by validating against the pre-seal bytes, with its own test. Resolves: iss-2609120446083912 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
Three findings deferred at the v0.8.0 cut as needing a human design decision have that decision now, each taken by the product thinker and each routed the way the decomposition rule asks, with the hand-run graded into the calibration note. The decisions log becomes a folder of individually minted records with a derived index, and the log's path a symlink to that index for now. The question first asked was how adoption should propagate the merge-attribute workaround into a managed repository; the answer came from the same run's positive finding, that the five one-file-per-record families never conflicted across twenty-seven merges while the two single appended files conflicted on nearly every one. The rule is an accepted decision record; the capability is an intent draft carrying the migration, the index, the adoption path and the retirement of the decisions-append gate as its acceptance criteria. An intent owns one or more specs and ships when its last spec closes. A spec that delivers part of a thought-through intent is closed on its own terms and a new spec is minted for the remainder; the intent moves only when no open spec names it. The rule is a decision record and invariant 17 of the brief; its build is owed, and the issue that asked for it stays open until it lands. Banned names get both halves: a committed declaration lifts the public visibility fence so a fresh public repository can create its committed list on its first commit, and a machine-global private list in the user-level home bans a name in every repository on the machine, never read by CI. Filed as an intent draft promoted from its issue. Refs: iss-2609100507439414 Refs: iss-2609100508570803 Refs: iss-2609100508566552 Refs: iss-2609100506269348 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
A spec composed during an autonomous run carried two invented record ids in its body, and nothing in the gates read a record's prose for the handles it names; a human reading the document back afterwards was the detector. The typed cross-references in frontmatter were already resolved, and links were already checked, and between them sat the thing a session actually writes: a sentence. record-lint gains prose_citation_resolves. It reads every record body and every free-text frontmatter field across the record stores, finds each handle in the cited-id grammar, folds the spelling a sentence used into the spelling the resolver keys on, and refuses one that names no record. There is one resolver in this binary and the rule adds no second. A slug does not stop an id being an id, so a filename-shaped handle is a citation of the id it starts with, and a hyphen before the family word does not make it part of a longer handle. An author marks a numeric placeholder or a forward reference on its own line with an illustrative or forward-looking marker, and the refusal message says so. The corpus that predates the gate is carried in a committed baseline keyed on the id, never on the file, since records move between status folders as a matter of routine. Each entry declares which class it is and why: a decision pruned by its successor, an id a predecessor implementation numbered and never minted here, a record living on an unmerged branch, and one probable defect carried as suspect for a human to correct. The baseline ratchets in both directions: a new unresolvable id fails even in a file the baseline names, and an entry nothing cites any more is reported so the list only shrinks. Sixteen existing illustrative sites are marked in place rather than licensed globally by id. Every new behaviour was watched failing first, and the review's findings on slug-continued handles, free-text frontmatter, the leading hyphen and the empty-baseline message were each closed with their own test. Resolves: iss-2609100518527863 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
…ng finds the ones it missed Four fixed issues sat open in a managed repository's ledger because the only evidence of their fix was commit prose the ledger never reads. A mention that must declare itself turns that prose into a signal both the gate and the ledger can read. RS004 joins the issue-resolution gate: a commit message, or a pull-request title or body, that names an issue id must carry a declaration line. A resolves line says the change fixes it, and the existing rule then requires the record to move in the same diff; a refs line says touched but not fixed and demands nothing of the ledger. The vocabulary is closed at those two words, a comma-separated list is admitted on either, and a near-miss is refused with the two spellings named. The commit half runs where the existing range check runs; the pull-request form half runs in the attribution workflow, the one that re-runs when a title or body is edited. Merge commits are exempt because the forge composes them. The hint for the backlog that predates the gate is a read-only listing: it walks the default branch and the ledger, and reports each open record whose id appears in a commit message without a resolution, ranked by what the commit did, silent on the commit that filed the record and on a refs declaration. It lists and never moves a record; the test asserts the ledger is untouched. Run here it finds seventy-eight rows and no undeclared resolves line, so the existing rule has held and the backlog is undeclared mentions. Every proof was watched failing first. The review's findings were closed with their own tests: a commit body can no longer forge a record boundary in the walk, since boundaries are checked against the commit graph; a ref that shares a name with a tracked path walks; each row's exemplar is its strongest commit; the shell scanners pin a C locale so a stray byte cannot hide a mention; and the list form on a resolves line is read by both rules, so the declaration that satisfies one cannot bypass the other. Resolves: iss-2609100507421759 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
…ec closes Closing a spec shipped its intent in the same step, with no way to separate them. A session met an intent whose criteria were only half delivered, stopped, and asked rather than close, which was right; the rule it was missing is now recorded and built. An intent that has been thought through stands as written. A spec that delivers part of it is closed on its own terms, a new spec is minted for the remainder and attached to the same intent, and the intent moves to shipped on the close after which no open spec names it, with the impact judgement demanded at that transition and no earlier. The spec's back-link is the one source of truth for which specs realise an intent; the intent's own link stays a scalar meaning the spec it was planned with, and the bidirectional check becomes membership. The set is derived by one canonical comparison shared by the spec store, the intent corpus and the record-lint index, because two readers answering "does this intent have an open spec" differently is the exact hole the review found: a zero-padded back-link let the store ship an intent the lint saw as still open, and left the second spec unclosable through any verb. The remainder is minted before any move, so a failure moves nothing, and a retry reuses an open remainder with that slug rather than minting a second. A remainder is refused on a shipped intent or a closed spec. The fidelity audit now names every spec that realised the intent, and the receipt stays owed once per intent. The release cut refuses a planned intent only when all its specs are closed, so a release taken mid-delivery is no longer walled off. Every behaviour was watched failing first, on a scratch copy, and the review's findings each closed with their own test. Eighty-six existing specs and eighty-five linked intents needed no edit. The dispatch pages list an intent's specs only when more than one realises it, so single-spec pages are unchanged. Resolves: iss-2609100508566552 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
…ing it to PATH The owned PATH copy was verified only against a binary-meta beside the artefact, in a directory named solely by CLAUDE_PLUGIN_DATA, so whoever controlled that variable could have `ahoy install` bless bytes of their choosing (GHSA-4q78-ccfv-f374, CWE-345). The bootstrap now writes ~/.abcd/cache-attestation (data dir, manifest-authenticated hash, trust, time; 0600; renamed in whole) only after it has authenticated the cache against the release manifest, and install promotes a cache only when the attestation names that directory and the co-located record carries the attested hash. An unbound cache is refused with a note naming which of the three checks failed, and install degrades to the pinned symlink as it does with no cache. An offline bootstrap writes nothing and leaves an existing attestation untouched. The plugin-root stamp route gets the same binding; dogfood checkouts are unaffected because the binding is on the cache, not the plugin-root binary. Refs: iss-2609012039102770 Assisted-by: Claude:claude-fable-5-1
iss-2609012039102770 resolves with impact fix. adr-2609151706587280 supersedes adr-46, carrying its five decisions forward under the same numbers and adding the cache attestation as the binding between an environment-supplied data directory and the provenance install records; adr-46 is marked superseded. spc-35's "taken from the harness or not at all" is revised in place, dated. Brief invariant 12 gains the clause. The decision log records that the maintainer ruled option B on 2026-09-15. Resolves: iss-2609012039102770 Assisted-by: Claude:claude-fable-5-1
…ue, never the record beside it The binding check compared the co-located binary-meta with the attestation and the promotion then re-read that same attacker-writable record to decide what the artefact must hash to. A writer in the attested directory who swaps the (artefact, binary-meta) pair for a self-consistent forgery between the two reads had the forgery promoted — the security review of the first cut did it in 0.25 s with a flipper goroutine. cacheBindingProblem now hands back the attestation it accepted, and installOwnedEntry takes its three verdicts once — hazard, presence, binding — and acts on those locals alone: `want` is the ATTESTED hash, and nothing under the data dir is consulted after the binding except the artefact bytes, which are hashed against it. A test seam (nil in production) occupies the window deterministically; TestInstallRefusesPairFlippedAfterBinding was watched failing with the forged pair promoted and no note, and passes with the pair refused on the checksum, no PATH entry and no provenance written. Assisted-by: Claude:claude-fable-5-1
…r a name it can predict The attestation temp was `$HOME/.abcd/.cache-attestation.$$`, written with `>` and chmod-ed by name; both follow a symlink pre-planted at that predictable name, so a same-UID writer could have the run write the record's bytes and mode onto a file of their choosing and then rename the planted link itself into place as the attestation. mktemp creates a fresh exclusive regular file under an unpredictable name and the chmod is by the name it returned. TestBootstrapAttestationTempIgnoresAPlantedSymlink learns the script's pid before it starts (a wrapper records $$, waits, then execs the script, which keeps the pid), plants a symlink at the old predictable name, and was watched failing with the victim's bytes and mode overwritten and the attestation landing as a symlink; it passes with the victim untouched and a regular 0600 attestation in place. Assisted-by: Claude:claude-fable-5-1
…write and on the read CLAUDE_PLUGIN_DATA is an environment value written raw into a line-oriented record whose Go reader parses last-wins and whose shell reader parses first-wins: a value carrying a newline injected key=value lines of its own, and a forged binary_sha256 line after the real one won in Go. The bootstrap now strips \000-\037\177 from the value before the write — the same class meta_field strips on read — and readCacheAttestation refuses a record whose data_dir holds one, since no bootstrap wrote it. TestBootstrapAttestationStripsControlCharactersFromDataDir (a data dir whose name carries a newline and a forged record line) was watched failing with a five-line record and the forged hash parsed; the two new TestReadCacheAttestationIgnoresMalformed cases were watched failing with the control-carrying record parsed. All pass. Assisted-by: Claude:claude-fable-5-1
…ript-capture # Conflicts: # .abcd/development/research/notes/2026-08-15-decomposition-calibration.md
…, mend links The widening and detection windows no longer fit the corpus, which this cycle grew by the records of six rulings, the merge of the roles design workstream and the lifecycle-symmetry intent. Each is re-measured at the merged tip and moved to the smallest ten-thousand boundary leaving at least one per cent headroom, the rule the entries already state; the kinds are untouched. Four prose-citation baseline entries carried ids that lived only on the design branch; the branch has landed and they resolve, so the ratchet's shrink half reported them spent and they go. One id written on main as an example of the minted id shape takes the rule's illustrative marker on its line. Three decision records and one intent linked issue records by their open path, which the resolutions of this cycle moved; the links now name the resolved path. Refs: iss-2609100508566552 Refs: iss-2609100518527863 Refs: iss-2609100507421759 Refs: iss-2609120446083912 Refs: iss-2609120452369809 Assisted-by: Claude:claude-fable-5-1
…ript-capture # Conflicts: # .abcd/development/brief/06-delivery/03-out-of-scope.md
The owned-copy attestation for GHSA-4q78-ccfv-f374, taken into v0.9.0 on the product thinker's ruling of 2026-09-15 so the advisory publishes with the release that carries its fix. Five commits from the peer session, reviewed there for security with three fixes; reviewed again here before the roll. Refs: iss-2609012039102770
…is cut The release guard refuses a cut carrying a major finding captured since the anchor and still open, and the merges of the design workstream and two peer pull requests brought eight such records into this branch. Each is answered on its own terms rather than waved through. Two are resolved on evidence. The roles page rewrite that landed today answers every complaint the README-and-roles-page finding made, except one phrase a later merge had silently dropped from the README, which is restored here. The finding that the conventions file still told authors ADRs keep a hand-numbered ordinal was fixed three days ago under its twin record, and is closed against that commit with an internal impact so one correction does not become two changelog lines. Six are deferred out loud, each with its own reason on the record. Four of the design workstream's findings about the fidelity audit, verification rungs and responsibility for delivered work are settled by decisions the workstream itself filed as drafts and an open request for comment, so building them now would answer in code the question the record routes to the product thinker. The finding that auto-release tags before the gate that can refuse it is renewed at this anchor with what changed since its first waiver: a second run falsified the premise the earlier ruling rested on, so the remedy is now the completion of an accepted ruling, wanted at the top of a cycle rather than at the end of one. The slug generator's trailing hyphen does not reproduce: every truncation site already trims the separator, two hundred thousand adversarial inputs produced no refused slug, and what remains of the record is a vocabulary ruling. Resolves: iss-2608290833558547 Resolves: iss-2609111002410678 Refs: iss-2608290820473197 Refs: iss-2608290822140563 Refs: iss-2608290944122400 Refs: iss-2608290956522870 Refs: iss-2609100513521322 Refs: iss-2609120452071388 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
…session can trust An independent security review of the owned-copy attestation found the new record held a lower bar than the record beside it. The attestation was read through the guarded read alone, while the path entry it stands before is read through the declaration read, which additionally refuses a record writable by group or other or owned by another uid. The attestation chooses the directory and the hash a release binary is promoted against, so a record anyone else could write was a record anyone else could aim. It now clears the same three-part bar, and the comment that claimed it already did is replaced with what the declaration read adds and why. The fix's safety argument also assumed the home directory is not something the environment can choose, and neither reader nor the shell writer pinned it: a relative home resolved against the checkout, so a committed record under it would have been read as the user's own. Both readers and the bootstrap now refuse a home that is unset, not absolute, or inside the repository being installed, through the one containment helper the data directory hazard already uses. A refused home is named as the reason, the install's remedy matches it instead of pointing at network access, and the bootstrap says why it wrote no record rather than saying nothing. The containment compare reads the real working directory, never the environment's word about it, which the existing origin-allowlist test enforces from the other side. One finding is captured rather than fixed: a stale attestation permits a downgrade to the last attested genuine release, bounded to a manifest-verified binary, since the record carries no freshness. Every behaviour was watched failing first on a scratch copy; the live home was never touched. Refs: iss-2609012039102770 Refs: iss-2609151847117977 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
The docs-currency pass over the v0.9.0 content commit found a user-facing verb with no line in the derived release notes: the outbound text check, which judges a commit message, pull-request body or release note against the outbound policy and is what CI runs over every message in a pull request's range. It landed in the change that fixed the session-URL leak into commits, under records that name the leak and not the verb, and the changelog composes only from terminal records. The record exists so the release notes can name what shipped; it is resolved against the commit that shipped it. Resolves: iss-2609152131205316 Assisted-by: Claude:claude-fable-5-1
…s where the print form refuses Two currency findings from the semantic pass over the release-content commit. The README's sample status board follows the adoption step, which leaves the repository managed, and a managed repository's board carries a fifth line naming the presence badge, the repository, the branch and the record counts; the sample now shows it. The mode page said an absent store reads as managed but not that the print form refuses outside any git checkout, which the command reference already states; the page now says both. Refs: iss-168 Assisted-by: Claude:claude-fable-5-1
The dated section is derived from the thirty-four records that reached a terminal folder since v0.8.0: three shipped intents and thirty-one resolved or declined issues, two of them breaking. The version is derived from the records' declared impact, the prose is the release-changelog composer's, and the binary proved the section cites exactly the cut before writing it. This is the release-content commit the two semantic gates read; their receipts follow in the next commit, naming this one. Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
Both host-run gates PROMOTE at full tier, which a breaking release requires. A receipt names the commit its reviewers read and cannot sit in that commit's tree, so this is the last commit of the release branch and dca2d77 is the content it attests. docs-currency-reviewer read the user-facing surface against a binary built from the first roll of the section: every line verified against the code with file and line evidence, and every user-facing page changed since v0.8.0 found current. Its three findings were all incomplete rather than false, and all three were fixed before the section was re-rolled: a shipped verb with no record and therefore no line, which is now recorded and named; the README's sample board, which now shows its presence line; and the mode page, which now says where the print form refuses. The receipt says exactly how the commit it names differs from the one read. The brief-surface crosscheck ran its 37 pinned checkers at full tier, five at a time, and returned 115 discrepancies, all dispositioned. Every one is design-record prose that ships in no binary; forty-three describe surfaces this cycle shipped whose brief chapter was not brought up with them, the rest were already divergent at the last release. Four armings have now returned 246, 137, 109 and 115, a fresh sample each time, so the systematic brief pass already recorded is the remedy and the receipt says so per finding. Refs: iss-2609091956001547 Refs: iss-2609152131205316 Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5-1
REPPL
enabled auto-merge
September 15, 2026 22:51
…scan allows CI's full-history secret scan refused the release pull request on five GitHub-PAT-shaped strings in the scanner's identity-span test, committed with the span-based masking fix. They are the same class the allowlist already names for the scanner's other unit test: realistic fakes the test exists to detect, never live credentials. The allowlist gains that one file by path, so the scan still fails on a real secret anywhere else. The newer gitleaks on this machine does not flag them, which is why the local gate stayed green; CI's pinned version does, and the pin is the one that gates the merge. Assisted-by: Claude:claude-fable-5-1
…carries The docs gate armed against the pull-request base found the intent-auditor definition edited without a prompt_version bump: the provenance change (the hash pair is echoed from the request's Provenance block, never computed) went in with its fix but no changelog entry could be keyed for it. This is a PATCH in the 0.x band: the verdict shape, the rubrics and every other ingest rule are untouched. The local gate runs unarmed, which is why preflight did not see it. Refs: iss-2609100505140261 Assisted-by: Claude:claude-fable-5-1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The v0.9.0 release branch: the release-content commit dca2d77 rolls the
changelog derived from the 34 records that reached a terminal folder since
v0.8.0, and the final commit records the two semantic gate receipts naming it.
The merge queue's method must stay merge: a squash would collapse the
two-commit shape the release gate reads.
Breaking, by two records: a refusal in machine-readable mode now answers on
standard output in a self-announcing envelope, and an intent owns one or more
specs, so closing a spec ships its intent only when no open spec remains.
Shipped in this cut:
same redacted record its main thread does; transcripts already on disk are
recovered into the repository that owns them; a captured session is handed
to an agent as one self-contained artefact.
whose answer the loop is waiting on, offered at install as its own consent.
spec close --remaindermints the follow-onspec, and the intent ships when its last spec closes.
exists, and a commit or pull request that names an issue must declare its
relation to it; a read-only listing finds the open records history already
mentions.
reverse-DNS identifier survives capture intact.
only when a home-scoped attestation vouches for it, read from a record and
a home the session can trust.
refusal, the dangling PATH link, the absent plugin manifest, and the rest of
the thirty-one fixes named in the changelog.
Eight findings the merges from main brought into this cut are answered on
their own terms: two resolved on evidence, six deferred with their own
recorded reasons. Three rulings taken this cycle are recorded as decision
records and intent drafts.
Verification
make preflightgreen on every tip today and on the receipts commit;make fmt-checkclean.after, each lane reviewed independently before it was committed, and the
attestation fix reviewed adversarially after merge, with its findings
closed on this branch.
proven locally:
go run ./cmd/record-lint --release-gate dca2d779… --require-gate docs-currency-reviewer --require-gate iss35-brief-surface-crosscheckexits 0.go run ./cmd/abcd changelog --jsonreportsready: true,next_tag: v0.9.0, impact breaking, no refusals.Assisted-by: Claude:claude-fable-5-1