| id |
type |
title |
status |
version |
created |
updated |
workplan_task |
depends_on |
related |
| RMGR-ARCH-CMD-0001 |
architecture |
Observation and command contracts v0.1 |
draft-reviewable |
0.1 |
2026-08-09 |
2026-08-09 |
RMGR-WP-0001-T02 |
|
| docs/repository-representation_v0.1.md |
| docs/observation-command-contracts_v0.1.yaml |
| INTENT.md |
| prj-state-hub-retirement/architecture/hub-extension-architecture_v0.1.md |
| prj-state-hub-retirement/architecture/information-model_v0.1.md |
|
Observation and command contracts v0.1
Purpose
Define versioned, persistence-free contracts for:
- Observation — normalized repository facts and work-record index reads
- Commands — governed mutations with authz, idempotency, correlation, and
Git-backed evidence
- Hub-core integration — how
port.repo and port.work expose these
without leaking SQLAlchemy models
Companion: observation-command-contracts_v0.1.yaml.
Implementation (OpenAPI generation, runtime) follows T04 foundation decision;
this document freezes semantics for T03 extraction and T05 E2E proof.
Contract identity
| Field |
Value |
contract_id |
helixforge.repo-manager |
contract_version |
0.1.0 (draft-reviewable) |
representation_ref |
RMGR-ARCH-REP-0001 |
| Compatibility |
Additive in minor; breaking requires major + dual-run |
Hard rule: Request/response DTOs are the public surface. Persistence
schemas, table names, and ORM classes are private to Repo Manager.
Common envelope
Every API message (HTTP/JSON, event payload, or MCP tool result) SHOULD carry:
api_version: "0.1"
correlation_id: "<UUIDv7>" # mint if client omits
request_id: "<UUIDv7>" # per call
actor:
type: human | agent | service
id: "agt-…" | "user-…" | "svc-…"
authz:
subject: "…" # identity principal
scopes: ["repo:read", "repo:command:…"]
decision: allow | deny # on response if evaluated
Correlation
- One human/agent action that spans observe → command → event uses one
correlation_id.
- Hub-core progress/interaction events that describe the same action reuse it
(IA dual-write ban still applies: one semantic fact, correlated kinds).
Idempotency
| Surface |
Key |
Behavior |
| Commands |
Idempotency-Key header or idempotency_key body (UUIDv7) |
Same key + same command type + same payload hash → return original result; conflict if payload differs |
| Observation |
none required |
Safe to retry; may return fresher observed_at |
| Writeback |
key includes repo_uuid + source_path + content hash |
Replay does not double-commit if evidence SHA already recorded |
Retention of idempotency records: ≥ 24h (operational policy may extend).
Evidence object
Returned on every completed command (success or handled failure after
partial work):
evidence:
status: accepted | applied | rejected | failed
git_sha: "…" | null # primary proof when applied
git_refs: ["refs/heads/…"] # optional
forge_pr_url: null | "…"
files_touched: ["path", …]
content_hashes: { "path": "sha256:…" }
observed_at: "ISO-8601"
notes: "non-secret"
Rule: status: applied without git_sha is invalid for file-mutating
commands (except pure operator-config commands that only change host_paths).
Failure model
| Code |
HTTP-ish |
Meaning |
Client action |
unauthorized |
401/403 |
Authn/authz failed |
Fix credentials/scopes |
not_found |
404 |
Unknown repo or record |
Re-resolve slug/uuid |
conflict |
409 |
Drift, lock, or idempotency payload mismatch |
Re-observe; resolve drift |
precondition_failed |
412 |
Stale expected_head_sha / base revision |
Refresh HEAD; retry |
validation_error |
422 |
Schema/vocab/command args invalid |
Fix request |
policy_denied |
403 |
Policy port deny |
Escalate / change policy |
unavailable |
503 |
Forge/git/policy/hub down |
Retry with backoff |
internal |
500 |
Unexpected |
Operator; attach correlation_id |
Failures are structured JSON, never raw stack traces to external clients.
error:
code: conflict
message: "Index drift on workplans/RMGR-WP-0001-foundation.md"
correlation_id: "…"
details:
finding_ids: ["…"]
retryable: false
Authorization context
Commands and sensitive observation paths require:
| Field |
Source |
subject |
Identity authority / hub-core addressing |
scopes |
Policy (examples below) |
repo_uuid or repo_slug |
Resource |
lane |
Optional autonomy lane for agent actors |
reason |
Required for high-risk commands |
Scope vocabulary (v0.1)
| Scope |
Allows |
repo:read |
Observation of representation + index |
repo:register |
Create/update registration & host_paths |
repo:reconcile |
Run consistency / rebuild projections |
repo:command:work_status |
File-backed status writeback (task/WP) |
repo:command:writeback_ids |
UUID / hub id frontmatter writeback |
repo:command:lifecycle |
lifecycle transitions (archive, …) |
repo:command:agent_assign |
coach/lead/director assignment |
repo:admin |
Break-glass operator |
Policy port: Repo Manager evaluates via hub-core port.policy (or
local allowlist in dev). It does not become the authorization authority.
Green-lane agents may hold only repo:read + narrow command scopes per
autonomy policy; non-green requires explicit human/policy allow.
Observation contract (port.repo / port.work reads)
Normalized fact types
| Fact |
Description |
Source of truth |
RepositorySnapshot |
ManagedRepository DTO (REP-0001) |
files + operator + git observation |
WorkRecordIndexEntry |
spine fields for one work record |
file + index |
ConsistencyFinding |
open C-rule style finding |
derived |
RevisionObservation |
head_sha, dirty, observed_at |
git |
AgentAssignment |
coach/lead/director |
file/registry projection |
SignalSet |
named derived signals |
derived |
Read operations
| Op |
Port |
Request |
Response |
GetRepository |
repo |
slug or uuid |
RepositorySnapshot |
ListRepositories |
repo |
filters: domain, category, lifecycle, tag |
page of snapshots |
ObserveRevision |
repo |
slug, optional host_id |
RevisionObservation |
ListWorkRecords |
work |
repo, kind?, status? |
page of index entries |
GetWorkRecord |
work |
uuid or (repo, canonical_id) |
entry + source_path |
ListFindings |
repo |
repo, open_only? |
findings |
GetSignals |
repo |
repo |
SignalSet |
Pagination: cursor or limit/offset with stable sort (slug / updated_at).
No ORM objects in responses.
Observation guarantees
- Read-your-writes: After
applied command with git_sha, a subsequent
GetRepository/GetWorkRecord within the same process generation MUST
reflect that revision or return observed_at older with explicit
stale: true and head_sha for client retry.
- Rebuild: Full reindex from files produces equivalent index content
(modulo mint of new UUIDs only for records never writeback-assigned).
Command contract (governed mutations)
Command lifecycle
accepted → (running) → applied | rejected | failed
| State |
Meaning |
accepted |
Validated, authz allow, idempotency recorded; not yet on Git |
running |
Optional intermediate for long ops |
applied |
Evidence includes git_sha (or config-only evidence) |
rejected |
Policy/validation; no mutation |
failed |
Started but did not complete cleanly; may need repair |
Clients MUST treat only applied as success for dependent steps.
Command envelope
command:
type: "repo.work.update_task_status" # cataloged
api_version: "0.1"
idempotency_key: "…"
correlation_id: "…"
repo: { slug: "repo-manager" } # or uuid
expected_head_sha: "…" | null # optimistic concurrency
actor: { type: agent, id: "…" }
reason: "session checkpoint"
params: { … } # type-specific
Command catalog (v0.1)
Registration & operator config
| Type |
Params |
Mutates Git? |
Scope |
repo.register |
slug, remote_url?, classification?, description? |
no* |
repo:register |
repo.set_host_path |
host_id, path |
no |
repo:register |
repo.set_lifecycle |
lifecycle, reason |
no** |
repo:command:lifecycle |
* May write registration notes only if a repo file policy says so (default: no).
** Lifecycle is projection+operator state; forge archive is separate/explicit.
Observation-triggered
| Type |
Params |
Mutates Git? |
Scope |
repo.reconcile |
full? paths? |
no (index only) |
repo:reconcile |
repo.rebuild_index |
kinds? |
no |
repo:reconcile |
File-backed work (writeback)
| Type |
Params |
Mutates Git? |
Scope |
repo.work.update_task_status |
task_id, status, blocking_reason? |
yes |
repo:command:work_status |
repo.work.update_workplan_status |
workplan_id, status |
yes |
repo:command:work_status |
repo.work.writeback_ids |
record_ref, uuid fields |
yes |
repo:command:writeback_ids |
Agents
| Type |
Params |
Mutates Git? |
Scope |
repo.agents.assign |
coach?, lead?, director? |
prefer yes (file) |
repo:command:agent_assign |
Command execution rules
- Authz first — deny before any file lock.
- Load observation — if
expected_head_sha set and differs → precondition_failed.
- Drift gate — mutating commands on a path with open high-severity finding
→
conflict unless force: true + repo:admin.
- Apply — single atomic Git commit per command preferred; message includes
correlation_id and command type.
- Reindex — update projections for touched paths.
- Emit — repository change event (IA:
repository_change_event) with
correlation_id; hub-core may project further.
- Never create workplans/tasks from telemetry or messages alone.
What is not a Repo Manager command
| Action |
Owner |
| Send agent message / inbox |
hub-core port.messaging |
| Append progress event |
hub-core port.events.progress |
| Schedule cron / ops run |
activity-core port.schedule |
| Merge PR on forge without Git in checkout |
forge + human policy (optional later adapter) |
| Policy decision |
policy service |
Events emitted
| Event type |
When |
Payload (non-secret) |
repo.registered |
register |
slug, uuid, domain |
repo.observed |
optional periodic |
slug, head_sha |
repo.reconciled |
reconcile done |
finding counts |
repo.command.applied |
command success |
type, git_sha, files |
repo.command.failed |
command fail |
type, code |
repo.work.indexed |
new/updated index entry |
kind, id, path |
repo.drift.detected |
new finding |
finding summary |
Consumers: hub-core projections, activity-core triggers. Payloads MUST NOT
include secrets, raw tokens, or full file contents by default (hashes + paths
only).
Hub-core integration
agents / domain hubs / MCP
│
▼
hub-core
port.repo │ port.work
│
▼
repo-manager
(this contract)
│
▼
Git checkouts + files
| Hub-core need |
Repo Manager op |
| Address a repository |
GetRepository |
| Orient on repo work |
ListWorkRecords + signals |
| Apply agent task status |
repo.work.update_task_status |
| Consistency automation |
repo.reconcile (often via activity-core) |
| Domain summary (partial) |
snapshots + work counts |
Hub-core must not:
- open Repo Manager DB connections;
- import
repo_manager.models;
- dual-write work status only to its own DB without command evidence.
During State Hub dual-run, a compatibility adapter may implement the same
command types by calling State Hub APIs, then flip to native RM (T03/T05).
MCP / CLI mapping (illustrative)
| User intent |
Contract |
statehub fix-consistency (today) |
→ repo.reconcile (+ legacy adapter) |
update_task_status MCP |
→ repo.work.update_task_status when cut over |
register-from-classification |
→ repo.register + classification observe |
Exact tool names land with runtime packaging (T04).
Conformance tests (minimum for T05)
| # |
Test |
| C1 |
GetRepository returns DTO without ORM fields |
| C2 |
Reconcile is idempotent on clean tree |
| C3 |
update_task_status with same idempotency_key replays |
| C4 |
Stale expected_head_sha → precondition_failed |
| C5 |
Deny without scope → unauthorized/policy_denied |
| C6 |
applied response always has git_sha for file commands |
| C7 |
Rebuild index matches prior entries for UUID-written records |
| C8 |
Event payload has correlation_id and no secret keys |
Acceptance (T02)