The checker JSON reports per repository repo_slug, source_revision (full
40-hex HEAD SHA) and source_revision_reason; C-25 findings repeat them so a
findings-only consumer can bind. A dirty workplans/ tree, a non-git path,
a missing HEAD or a tree changing mid-scan yields null plus a reason, never
HEAD or a branch name. plan_owner_returns validates and passes the revision
into every candidate and refuses findings bound elsewhere.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 692292@bnt-lap001
Assistant-Session: 89c7d8e8-e1b3-47eb-9411-a0ab227fca2f
GET /messages/ accepts offset with stable created_at,id ordering; C-25 pages
every reply per counterpart, dedups by message id and warns explicitly when
collection is incomplete instead of capping at 200.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 671493@bnt-lap001
Assistant-Session: 60d7b11f-78f9-4731-9367-918511b2762d
Blocked-with-nothing-waiting is a stale status; warn until a qualified
wait task exists or the workplan is re-statused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 237582@bnt-lap001
Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
Migration f9a0b1c2d3e4 (revises e8f9a0b1c2d3) adds tasks.blocked_on
(String 200) and tasks.wait_until (Date); TaskCreate/Update/Read carry both.
Read model (api/services/wait_kind.py): a wait task is external when it has
a dependency edge, a blocked_on matching ^message-from: (rule 6), or a
wait_until date (a window clears by itself). human/both/unqualified and
blocked_kind are unchanged in shape.
fix-consistency: _task_wait_fields carries blocked_on and wait_until (ISO
date, PyYAML date objects normalised), so the C-06/C-11 creates and the
C-10 status PATCH send them, and C-41 compares and syncs them like the
other qualifiers. C-39 accepts wait_until: YYYY-MM-DD as a qualifier; C-40
also fires when wait_until is before UTC today (report only).
/state/deps: depends_on stubs gain from_task_id / from_task_title so
consumers see task-scoped edges; both optional, response otherwise as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 237582@bnt-lap001
Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
Guards a checker newer than the hub it talks to; the primary silently
drops unknown fields and would store a workplan-level edge that later
duplicates the task-scoped one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 237582@bnt-lap001
Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
Task-block depends_on inside finished/archived plans or on done/cancel
tasks is ordering history, not a live commitment; indexing it produced
29 spurious fixable rows on the-custodian alone.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 237582@bnt-lap001
Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
consistency_check.py:
- C-20 now indexes each ```task block's depends_on (workplan ids and
task ids) as workplan_dependencies rows with from_task_id set,
description "<task> depends_on <target>". Targets may live in other
repos: resolved by file mapping first, ADR-007 derived uuid second;
unresolvable targets stay a non-fixable C-20 as before. Frontmatter
edges are unchanged (keyed with from_task_id None).
- C-39 task-wait-unqualified (WARN, not fixable): wait task with neither
task-block depends_on nor needs_human: true. Skipped in closed
workplans.
- C-40 task-wait-blocker-satisfied (WARN, not fixable): every depends_on
target terminal (tasks done/cancel, workplans finished/archived), or
decision_id resolved in the hub. A target or decision the hub cannot
resolve is skipped and withholds the warning; decision_id resolves via
a uuid, the repo's state_hub_decision_id writeback, or the derived id.
- C-41 task-wait-qualifier-drift (WARN, fixable): needs_human,
blocking_reason, decision_id file -> hub via PATCH /tasks/{id}, only
for blocks that carry at least one of the keys so hub-set flags on
silent blocks are left alone. The same fields ride along on the C-10
status PATCH for wait tasks and on the C-06/C-11 task creates. When
needs_human is set and the block has no intervention_note, the
blocking_reason stands in (TaskCreate/Update require the note).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 237582@bnt-lap001
Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
No fleet regeneration: standing notices are already visible to agents on
old instructions, and the notice carries its own ack command.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
CUST-WP-0072-T03: new workplans from intake promotion and register
bootstrap declare flavor and depends_on.
Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
STATE-WP-0092: persist flavor from files, treat depends_on as the C-20
canonical edge, exclude flavor=residual from summary/next_steps/deps
unless include_residuals is set. Live primary still needs the alembic
revision applied.
Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
STATE-WP-0091: refuse Helm when remaining CPU cannot cover the API
surge, migrate hook, or unrelated pending demand. 65m fails, 105m is
narrowly sufficient, not factory admission.
Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
C-33 returned early when the generator produced no rows, so a leftover
index kept listing archived workplans as active. Rewrite that file to an
empty table instead of leaving the stale rows in place.
Assistant: grok
Assistant-Session: 01a04d7c-846d-77e3-af8d-020019e4eb61
The upgrade failed as `pre-upgrade hooks failed: timed out waiting for the
condition`. The migrate hook was in ImagePullBackOff: tag main-11f689d does not
exist. `.forgejo/workflows/image.yaml` has a `paths:` filter that excludes
deploy/**, so the chart-only commit was green in CI and built nothing — the two
green runs on it were host-smoke and container-smoke, not build-and-push.
470ece8 carries all the code; every commit after it touches only the chart and
generated docs.
scripts/verify_image_pin.py refuses a pin whose commit has no successful
build-and-push run, and says so in those terms rather than as a timeout five
minutes later. Verified against both the bad pin and the good one; treats an
unreachable forge as inconclusive rather than as failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
The API validates repo classification against the-custodian canon allowed
values. A container has no such checkout, so every classification write failed
with a 500 and classification could only ever be written from a workstation.
Mounts the vocabulary as a ConfigMap and points
REPO_CLASSIFICATION_ALLOWED_PATH at it.
The copy is the risk, so it is owned rather than trusted:
scripts/sync_classification_allowed.py regenerates it from canon and --check
fails on drift. make check-classification-allowed and
make railiance-state-hub-render both refuse to proceed when the copy diverges,
so a release cannot silently validate against a stale vocabulary.
The container volumeMounts and env blocks are merged rather than appended —
a second pair would have produced duplicate YAML keys as soon as sweep was
re-enabled.
Refs CUST-WP-0067-T09
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
capture_legacy_meter_evidence.py fell back to hours=8 whenever --days was
omitted, and --hours itself defaulted to 8. Every unattended capture sampled
06:00Z-14:00Z while writing a file named weekly-review with cadence: weekly.
39 of 40 captures ran this way; only 2026-07-08 used a true 7-day window.
Calls outside the band were never sampled, so interfaces with live callers
reported as retirement candidates -- GET /tasks/?workstream_id was flagged on
2026-08-19 despite traffic on 2026-08-18.
Default the script to days=7; keep --hours for spot checks, documented as not
retirement evidence. Adds corrected capture for 2026-08-20 and records the
residual gap (candidate rule ignores last_seen_at) against STATE-WP-0079-T05.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
STATE-WP-0080-T02: statehub register routes project-flavor scaffolding
through rmgr scaffold and keeps only repo + host-path registration.
T01 refuse remains when GOAL.md is missing and --wp-prefix is not set.
RMGR-WP-0005-T01: C-06/C-11/C-32 skip mint+writeback unless this
instance is the identifier registrar (STATEHUB_REGISTRAR or railiance
hostname).
First cut of the transclusion-style index named in the stage-3 seed: a
WORK-RECORDS.md at repo root listing kind/id/status/lane/source for
every workplan, task, intake, decision, and engagement found in the
repo -- derived purely from files (no hub query), matching the ADR-001
rebuild principle. Full markitect transclusion rendering is a
follow-on, not this first cut.
- _generate_work_record_index(): reuses iter_workplan_files/
parse_frontmatter/get_tasks_from_workplan for workplan+task rows,
and the repo-wide yaml-block scan already proven by C-31/C-32 for
intake/decision/engagement rows. Archived workplans excluded (index
is for current orientation, not history); closed decisions/intakes/
engagements stay listed. Returns None (no file written) for repos
with zero work records, to avoid clutter.
- _check_work_record_index_freshness(): C-33, WARN+fixable when
WORK-RECORDS.md is missing or its content differs from a fresh
regeneration.
- fix_repo C-33 dispatch: overwrites the file with the regenerated
content.
13 new tests (generation across all kinds, sort order, archived
exclusion, self-scan exclusion, freshness detection incl. idempotence).
No regressions: full repo suite green (561 tests).
Live-verified at two scales: binky-control (5 workplans, 31 tasks, 3
intake, 4 decisions, 5 engagements) and the-custodian (52 workplans,
332 tasks) -- both generated correctly on first run, confirmed
idempotent (identical second run produces no C-33 fix), and the larger
repo's scan added no material overhead to the existing ~1.5min
fix-consistency run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mechanism named in canon/standards/work-record-types_v0.1.md:
"Promotion is a first-class transition... manual transcription of an
intake item into other kinds is a process defect." This is what AWQ-010
needed and didn't have -- a human/agent had to notice, transcribe, and
re-register it by hand. One call now does what that manual pass did.
scripts/promote_intake.py: intake.routed -> workplan | task | decision |
engagement.
- workplan: new ADR-001 file at workplans/{ID}-{slug}.md, registered
against the hub (repo+topic resolution, POST /workplans, frontmatter
id write-back)
- task: appended as a ```task``` block to an existing --workplan-file,
registered via POST /tasks, reuses the existing
_inject_task_id_into_block writeback helper
- decision: appended as a ```yaml``` block with a fresh
{PREFIX}-DEC-{YYYY}-{NNN} id to --target-file, registered the same
way C-32 registers decisions (reuses _inject_yaml_block_field)
- engagement: appended as a ```yaml``` block with a fresh
{PREFIX}-ENG-{YYYY}-{NNN} id -- file-only, no hub entity exists yet
(same honest deferral as C-32), reported not silently skipped
In every case the intake is closed with outcome=promoted and
promoted_to=<new canonical id>; the new record carries an
origin: "intake:<id>" back-link.
Wired as `statehub promote-intake <intake-id> --to <kind> --repo-slug
<slug> --repo-path <path> --domain <domain> [--target-file ...]
[--workplan-file ...]`, matching the CLI shape named in the workplan text.
17 tests: pure helpers (_slugify, _next_number, _append_yaml_block,
frontmatter injection) offline; full promote_intake() flow with the hub
API mocked.
Live-verified against the real running API/DB and a real repo
(binky-control), not just mocks -- and the live proof caught a real bug:
the first workplan-promotion run silently produced a false success (the
intake was closed outcome=promoted, but /workplans/ actually 422'd on a
missing repo_id that the code never resolved, so no workstream was ever
created). Fixed to resolve repo_id via /repos/{slug} and to raise loudly
on registration failure instead of writing a half-registered file
silently; locked in as two regression tests. Re-verified clean:
workplan promotion (CLI direct + through `statehub promote-intake`
itself) and decision promotion both proven end-to-end against the live
hub, with all scratch artefacts (files + hub rows) cleaned up afterward.
No regressions: full state-hub suite (271 tests across
test_promote_intake, test_intake, test_work_record_registration,
test_work_record_check, test_routers_core, test_consistency_check,
test_consistency_sweep, test_mcp_smoke, test_mcp_write_tools) green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends fix-consistency to scan any file for kind: intake / kind: decision
YAML blocks (not just workplans/, per canon: any file is a potential
work-record source), create the corresponding hub entity when missing a
state_hub_intake_id / state_hub_decision_id, and write the id back into
the source block -- same write-back pattern as C-06 for workplans.
kind: engagement is reported INFO (deferred, not fixable): no hub entity
exists for it yet, a separate stage-3 follow-on.
- _load_work_record_kind_registry(): kind-aware registry loader, factored
out so C-31's existing flat _load_work_record_patterns() builds on it
without duplication (verified: C-31's 16 tests still pass unmodified)
- _check_work_record_registration(): detection, wired into check_repo
right after C-31
- _inject_yaml_block_field(): write-back helper for fenced
blocks, mirroring _inject_task_id_into_block's pattern for
blocks
- fix_repo C-32 dispatch: creates the intake (scoped to repo_id) or
decision (scoped to resolved topic_id, reusing C-06's domain->topic
resolution) via the REST API, then writes the id back
- tests/test_work_record_registration.py: 15 tests (classification,
detection incl. engagement-deferred and workplan-kind-exclusion,
injection incl. idempotence and non-interference with sibling blocks)
Live-verified end to end against a real registered repo (binky-control,
not just synthetic fixtures): a real fix-consistency run found and
registered 3 genuinely open, previously-unlinked intake items
(AWQ-002/003/006) sitting in AutopilotWorkQueue.md, and correctly
deferred 5 real OH- engagement items as INFO. No regressions: full
consistency_check + consistency_sweep suite (128 tests) and C-31's own
suite (16 tests) still green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Warn on YAML-block ids matching no kind in the canon work-record type
registry (~/the-custodian/canon/standards/work-record-types.yaml, override
via WORK_RECORD_REGISTRY). Detection only; registration of non-workplan
kinds is a later stage. Skips template placeholders and repos without the
registry available.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T03: MCP alias retirement gate was unstartable — deprecation notices existed
but no mcp:* interfaces were ever registered or metered. Add
_meter_legacy_mcp() posting /legacy-meter/usage (kind mcp_tool, component
state-hub.mcp) from create_workstream, list_workstreams, update_workstream,
update_workstream_status, and state://workstreams/{topic_slug}; register all
five phase-3 backlog keys so zero-window streaks accrue from today.
T04: trace residual POST /progress/ workstream_id (3 calls/8h, unknown) to
.custodian-brief.md labelling workplans "workstream_id:"; brief generator now
prints "workplan_id:". Briefs refresh on each repo's next fix-consistency run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expose hours query param on /legacy-meter/summary and weekly-review;
capture_legacy_meter_evidence.py defaults to --hours 8 (--days 7 for
weekly retirement gate). Re-capture post-deploy evidence with tighter window.
Add X-StateHub-Component to fix-consistency and other State Hub REST callers for
legacy-meter attribution. Archive STATE-WP-0069; open STATE-WP-0070 for
meter-gated phase-2 retirement. Task POST bodies use workplan_id only.