From 1437999eb76402747a9fe3a81bbeee41afb446cb Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 9 Aug 2026 22:06:46 +0200 Subject: [PATCH] docs(RMGR-WP-0001): complete T02 observation and command contracts Define helixforge.repo-manager 0.1: observation ops, governed command catalog, authz scopes, idempotency, evidence, failures, and hub-core ports. --- README.md | 1 + docs/observation-command-contracts_v0.1.md | 387 +++++++++++++++++++ docs/observation-command-contracts_v0.1.yaml | 149 +++++++ workplans/RMGR-WP-0001-foundation.md | 7 +- 4 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 docs/observation-command-contracts_v0.1.md create mode 100644 docs/observation-command-contracts_v0.1.yaml diff --git a/README.md b/README.md index ef59ad6..1526fd4 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,4 @@ Start with [INTENT.md](INTENT.md), [SCOPE.md](SCOPE.md), and the Architecture: - [Repository representation v0.1](docs/repository-representation_v0.1.md) +- [Observation and command contracts v0.1](docs/observation-command-contracts_v0.1.md) diff --git a/docs/observation-command-contracts_v0.1.md b/docs/observation-command-contracts_v0.1.md new file mode 100644 index 0000000..8cab0d5 --- /dev/null +++ b/docs/observation-command-contracts_v0.1.md @@ -0,0 +1,387 @@ +--- +id: RMGR-ARCH-CMD-0001 +type: architecture +title: "Observation and command contracts v0.1" +status: draft-reviewable +version: "0.1" +created: "2026-08-09" +updated: "2026-08-09" +workplan_task: RMGR-WP-0001-T02 +depends_on: + - RMGR-ARCH-REP-0001 +related: + - 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: + +1. **Observation** — normalized repository facts and work-record index reads +2. **Commands** — governed mutations with authz, idempotency, correlation, and + Git-backed evidence +3. **Hub-core integration** — how `port.repo` and `port.work` expose these + without leaking SQLAlchemy models + +Companion: [`observation-command-contracts_v0.1.yaml`](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: + +```yaml +api_version: "0.1" +correlation_id: "" # mint if client omits +request_id: "" # 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): + +```yaml +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. + +```yaml +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 + +```text +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 + +```yaml +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 + +1. **Authz first** — deny before any file lock. +2. **Load observation** — if `expected_head_sha` set and differs → `precondition_failed`. +3. **Drift gate** — mutating commands on a path with open high-severity finding + → `conflict` unless `force: true` + `repo:admin`. +4. **Apply** — single atomic Git commit per command preferred; message includes + `correlation_id` and command type. +5. **Reindex** — update projections for touched paths. +6. **Emit** — repository change event (IA: `repository_change_event`) with + correlation_id; hub-core may project further. +7. **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 + +```text + 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) + +- [x] Normalized observation facts and read ops defined +- [x] Command catalog with lifecycle, scopes, evidence +- [x] Idempotency, correlation, failure model specified +- [x] Authz context and policy port boundary stated +- [x] Hub-core port mapping without persistence leakage +- [x] Machine-readable companion YAML +- [ ] Runtime OpenAPI + automated suite (T04/T05) diff --git a/docs/observation-command-contracts_v0.1.yaml b/docs/observation-command-contracts_v0.1.yaml new file mode 100644 index 0000000..29e9110 --- /dev/null +++ b/docs/observation-command-contracts_v0.1.yaml @@ -0,0 +1,149 @@ +# RMGR-ARCH-CMD-0001 +id: RMGR-ARCH-CMD-0001 +contract_id: helixforge.repo-manager +contract_version: "0.1.0" +status: draft-reviewable +created: "2026-08-09" +document: docs/observation-command-contracts_v0.1.md +representation_ref: RMGR-ARCH-REP-0001 +workplan_task: RMGR-WP-0001-T02 + +rules: + - no_orm_in_public_dtos + - applied_file_commands_require_git_sha + - authz_before_mutation + - dual_write_same_fact_forbidden + - secrets_never_in_events + +common_envelope_fields: + - api_version + - correlation_id + - request_id + - actor + - authz + +idempotency: + commands: + key: idempotency_key + retention: ">=24h" + same_key_different_payload: conflict + +evidence_fields: + - status # accepted | applied | rejected | failed + - git_sha + - git_refs + - forge_pr_url + - files_touched + - content_hashes + - observed_at + - notes + +error_codes: + - unauthorized + - not_found + - conflict + - precondition_failed + - validation_error + - policy_denied + - unavailable + - internal + +scopes: + - repo:read + - repo:register + - repo:reconcile + - repo:command:work_status + - repo:command:writeback_ids + - repo:command:lifecycle + - repo:command:agent_assign + - repo:admin + +observation: + fact_types: + - RepositorySnapshot + - WorkRecordIndexEntry + - ConsistencyFinding + - RevisionObservation + - AgentAssignment + - SignalSet + operations: + - id: GetRepository + port: port.repo + scope: repo:read + - id: ListRepositories + port: port.repo + scope: repo:read + - id: ObserveRevision + port: port.repo + scope: repo:read + - id: ListWorkRecords + port: port.work + scope: repo:read + - id: GetWorkRecord + port: port.work + scope: repo:read + - id: ListFindings + port: port.repo + scope: repo:read + - id: GetSignals + port: port.repo + scope: repo:read + +commands: + lifecycle_states: [accepted, running, applied, rejected, failed] + catalog: + - type: repo.register + scope: repo:register + mutates_git: false + - type: repo.set_host_path + scope: repo:register + mutates_git: false + - type: repo.set_lifecycle + scope: repo:command:lifecycle + mutates_git: false + - type: repo.reconcile + scope: repo:reconcile + mutates_git: false + - type: repo.rebuild_index + scope: repo:reconcile + mutates_git: false + - type: repo.work.update_task_status + scope: repo:command:work_status + mutates_git: true + params: [task_id, status, blocking_reason] + - type: repo.work.update_workplan_status + scope: repo:command:work_status + mutates_git: true + params: [workplan_id, status] + - type: repo.work.writeback_ids + scope: repo:command:writeback_ids + mutates_git: true + - type: repo.agents.assign + scope: repo:command:agent_assign + mutates_git: preferred + +events_emitted: + - repo.registered + - repo.observed + - repo.reconciled + - repo.command.applied + - repo.command.failed + - repo.work.indexed + - repo.drift.detected + +hub_core: + ports: [port.repo, port.work] + must_not: + - open_repo_manager_db + - import_orm_models + - dual_write_work_status_without_command_evidence + +conformance_min: + - C1_dto_no_orm + - C2_reconcile_idempotent + - C3_command_idempotency_key + - C4_stale_head_precondition + - C5_scope_deny + - C6_applied_has_git_sha + - C7_rebuild_index + - C8_events_correlation_no_secrets diff --git a/workplans/RMGR-WP-0001-foundation.md b/workplans/RMGR-WP-0001-foundation.md index 78049ba..4ca8a56 100644 --- a/workplans/RMGR-WP-0001-foundation.md +++ b/workplans/RMGR-WP-0001-foundation.md @@ -51,7 +51,7 @@ records; signals; agent roles; State Hub `managed_repos` column map for T03. ```task id: RMGR-WP-0001-T02 -status: todo +status: done priority: high state_hub_task_id: "2453316f-33f4-41d0-bcb5-c7552db88d68" ``` @@ -60,6 +60,11 @@ Define normalized repository facts, governed commands, authorization context, idempotency, correlation, evidence, failure behavior, and hub-core integration without exposing persistence models. +**Result (2026-08-09):** `docs/observation-command-contracts_v0.1.md` + YAML +(`helixforge.repo-manager` 0.1 / RMGR-ARCH-CMD-0001). Observation ops, command +catalog, scopes, evidence, failures, events, hub-core port.repo/work mapping. +Runtime OpenAPI deferred to T04/T05. + ## Inventory extraction candidates ```task