repo-manager/docs/custodian-fleet-input-contract.md
tegwick f0410c57ef feat: prepare disabled fixed-policy Custodian snapshot endpoint
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 17:56:15 +02:00

9.5 KiB

Custodian fleet input publication contract

Owner recommendation, 2026-10-19; source preparation only. Repo Manager owns commit selection and work-record publication. Activity Core owns scheduling, private consumer storage, admission and run evidence. Custodian owns daily report semantics. These are proposed defaults, not runtime acceptance or an activation.

Reuse and exact missing boundary

rmgr freshness run --limit N --max-seconds S already observes Forge default HEAD, verifies the primary Hub projection and writes durable repo-manager.freshness-receipt.v1 receipts. It explicitly makes no checkout. The classification publisher's snapshots contain metadata, not work-record files. Neither command currently supplies a filesystem tree to Custodian commands. Do not reinterpret Hub rows or a classification snapshot as those input files.

The new offline prerequisite is:

python -m repo_manager.fleet_snapshot --request selected-commits.json --output snapshots/<unique-run-id>

It reads existing local Git objects, never fetches, checks out, changes a repo, contacts Hub/Forge, or reads a credential. This is a module CLI rather than a new daemon or scheduler. The publication owner supplies object stores using its existing approved Git delivery path. Choosing that runtime source/access path is still required; the exporter itself needs no network or authentication.

Request contract:

{
  "observed_at": "2026-10-19T07:00:00+02:00",
  "repositories": [
    {"slug": "the-custodian", "path": "/accepted/local/object-store/the-custodian", "commit": "<full-40-character-commit>"}
  ]
}

The example commit is a placeholder, deliberately not runnable. The publisher must retain each matching Forge observation/receipt ID separately; observed_at is supplied evidence, not a freshness assertion made by the offline exporter. Only workplans/**/*.md (including archived dependencies) and decisions/*.md are copied. Git symlinks, submodules at selected record paths, invalid slugs, non-full commit IDs, absent workplans and oversize records are refused. Dirty working files, .git, credentials and unrelated files are not copied. Limits are 2MiB per record and 64MiB total per export; exceeding either is an explicit publication failure, never partial accepted coverage.

Each new unique output contains <slug>/workplans, optional <slug>/decisions, manifest.json and finally COMPLETE. Manifest schema repo-manager.fleet-input.v1 records the requested observation time, all commits, file byte lengths and SHA-256 values. freshness_verified: false is intentional. COMPLETE contains the manifest SHA-256. Consumers must validate this marker, manifest and every file before accepting a publication. Failed promotion leaves no COMPLETE and must never be consumed. Names are never overwritten, even empty existing directories. The owner protects the output parent against concurrent untrusted writers. This is completion-marked publication, not a crash-durable transaction or automatically enforced read-only store. Do not change published files; mount the selected immutable directory read-only for the consumer.

Initial scope is the existing report estate: the-custodian, net-kingdom, railiance-platform, activity-core, access-engine, secrets-engine, ops-mason, ops-warden, key-cape, user-engine, tenant-engine, gate-house, kings-guard, policy-nexus and rapp-policy-nexus. This is a proposed explicit allowlist, not permission to publish every registered repo. The same selected set bounds both workplan reporting and decision synchronization. Review additions for cross-repo dependency completeness; missing dependencies remain unknown.

Recommend one pre-run publication step in the existing native daily workflow, not a separate timer. Reuse Repo Manager's Forge HEAD/primary receipt verification for the selected set, then export those exact commits from accepted object stores. rmgr freshness run --repos <comma-separated-set> --limit 15 --max-seconds <budget> now targets exactly the approved active set, rejecting missing active repos or a limit smaller than the set. The default untargeted mode still rotates fleet members. The publisher must inspect deferred/failure results and join successful per-repo receipts for every selected repo; command invocation alone is not complete coverage. No fabricated receipt or forced reconciliation is acceptable.

Recommended admission: observations at most 2 hours old at daily start, no future observation beyond 5 minutes of clock tolerance, all required repos present, full commits matching the verified observations, exact file hashes, and COMPLETE valid. Old commit age is not failure when its HEAD was recently re-observed. Never rewrite an old snapshot's timestamp to make it fresh. On missing/stale inputs, fail before security_flow_daily.py --apply, retain a sanitized missing repo/reason receipt and notify through the existing Activity Core failure path. Do not silently reuse yesterday's tree. Activity Core now requires the consumer freshness/content gate and supports immutable descriptor selection. The remote capture/transfer publication step is still unimplemented; neither local source test proves runtime publication.

Hold a specific snapshot path throughout a run and its retries. Avoid current symlinks (executor rejects them) and in-place directory replacement. Placement must supply an immutable per-run path or a serialized binding update while consumers are quiescent. Activity Core's prepared consumer selects an immutable child directory from a regular descriptor carrying manifest/receipt hashes. The receiver must finish retaining both artifacts before atomically replacing that descriptor. Transport and receiver implementation remain required before activation.

Retention and weekly boundary

Recommend retaining 35 days of daily publications plus every snapshot referenced by an unresolved failure, checkpoint, audit or accepted recommendation. Capacity planning must use measured export sizes, not assume the shared 1Gi claim suffices. Pruning is owner-reviewed initially; this exporter performs no deletion. Keep manifest/receipt hashes with run evidence and back up private state separately. Restore testing and capacity alarms are delivery gates, not established here.

Weekly sizing uses the existing cluster, resource-control, telemetry, inventory, provenance and optional previous-report/review inputs. Repo Manager must not manufacture telemetry or impersonate those collectors. Recommend retaining at least 13 weekly bundles plus all referenced/open recommendation evidence, with evaluation windows preserved. Custodian owns the weekly request and collector coverage validation; Activity Core owns immutable request handoff and retained report storage. Daily Git snapshots do not satisfy weekly measurement coverage.

Owner handoff and validation

Repo Manager recommendation: use this bounded offline export after the existing Forge receipt checks, with the explicit 15-repo set and 2-hour admission default. Activity Core should add the manifest/freshness gate and per-run immutable binding when implementing the chosen worker placement; never loosen symlink validation. No public API, broad clone service, extra schedule or new credential path is needed. Production receipt verification, object-store provisioning, consumer gate, retention/storage acceptance and first scheduled evidence remain unfinished.

Local tests use temporary Git repositories and prove committed-byte export despite dirty files, strict path selection, hashes/completion marker, immutable names, invalid-request refusal, selected symlink refusal and size bounds. They perform no network, production reads, schedule changes or Hub writes.

Wind-down checkpoint: disabled remote capture source

custodian_snapshot.py and the existing runtime now prepare a fixed-policy POST /ports/repositories/custodian-snapshot endpoint. Default is404/disabled. Configuration requires REPO_MANAGER_CUSTODIAN_SNAPSHOT_ENABLED=true, REPO_MANAGER_CUSTODIAN_SNAPSHOT_POLICY and REPO_MANAGER_CUSTODIAN_SNAPSHOT_HUB; existing PublisherSettings supplies the authorized Forge reader. Existing optional API-token admission is preserved. No caller paths/refs/repos/commands are used. No source endpoint is deployed.

Capture targets the complete configured set through native freshness, then reads allowlisted files at those exact commits through Forge contents API. It verifies Git blob identity, refuses unexpected/symlink files and returns a content- addressed manifest/receipt/file bundle. Default hard payload bound8MiB and per-response bound3MiB fail closed. Publisher state has no new persistent volume.

Validation: 47 publisher/capture/freshness/exporter tests passed with mocked HTTP; one existing Starlette/httpx deprecation warning. Source lint review remains a restart check. The source endpoint uses a process-local concurrency lock and 240-second native budget; it does NOT cancel producer work on client disconnect or retain durable in-progress receipt state. Do not treat it as ready for the proposed45-second combined daily pre-run phase. Existing15-repo capture timing has not been measured. Activity Core receiver is standalone and unintegrated; see its k8s/custodian-release/RESTART.md for exact restart steps.

The core-hub publisher's existing CCR0031 reader remains in that namespace. New Activity Core access to private workplan/decision content and publisher-to-Hub egress require the explicit source-reviewed data/network delta before live promotion. No credential route, access scope or schedule was changed here.