14 KiB
| 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 |
|
|
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.repoandport.workexpose 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
appliedcommand withgit_sha, a subsequentGetRepository/GetWorkRecordwithin the same process generation MUST reflect that revision or returnobserved_atolder with explicitstale: trueandhead_shafor 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.create_workplan |
workplan_id, title, goal, status?, owner?, domain?, topic_slug? | yes | repo:command:workplan |
repo.work.update_workplan |
workplan_id, title?, status?, owner?, domain?, topic_slug? | yes | repo:command:workplan |
repo.work.archive_workplan |
workplan_id, confirm_archive | yes | repo:command:workplan |
repo.work.writeback_ids |
record_ref, uuid fields | yes | repo:command:writeback_ids |
repo.register.upsert_entry |
kind, entry_id, title?, status?, data? | yes | repo:command:register |
repo.register.defer_entry |
kind, entry_id, status? | yes | repo:command:register |
repo.register.add_note |
kind, entry_id, note, author? | yes | repo:command:register |
repo.work.archive_workplan is the recoverable delete operation: it sets the
canonical status to archived and moves the file under the dated
workplans/archived/ convention. It does not erase repository history.
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_shaset and differs →precondition_failed. - Drift gate — mutating commands on a path with open high-severity finding
→
conflictunlessforce: true+repo:admin. - Apply — single atomic Git commit per command preferred; message includes
correlation_idand 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)
- Normalized observation facts and read ops defined
- Command catalog with lifecycle, scopes, evidence
- Idempotency, correlation, failure model specified
- Authz context and policy port boundary stated
- Hub-core port mapping without persistence leakage
- Machine-readable companion YAML
- Runtime OpenAPI + automated suite (T04/T05)