ChangeOps analyzes operational and policy changes, identifies affected people and systems, recommends evidence-backed actions, and preserves an auditable decision trail.
Milestone 7 PR C, Trusted Relationship Provenance.
The catalog explorer now records and exposes honest row-level origin for the 12 policy dependency rows owned by the deterministic demonstration seed. Each relationship detail separates its seeded source, owning authority, provenance timestamp, trust basis, and explicit absence of AI involvement. The existing typed foreign keys, relationship identities, assessment behavior, Confluence identity, comparison, approval, Learning System, and Jira behavior remain unchanged.
Milestone 0 is complete and preserved by the v0.0.1-milestone-0 tag.
See:
docs/product-brief.mddocs/milestone-0.mddocs/milestone-1.mddocs/milestone-2.mddocs/milestone-3.mddocs/milestone-4.mddocs/milestone-5a-policy-comparison.mddocs/milestone-5b-enterprise-impact-delta.mddocs/milestone-6.mddocs/milestone-7-enterprise-knowledge-catalog.mddocs/demo-scenario.mddocs/decisions.mddocs/aws-deployment.md- Contributing and engineering standards
See docs/roadmap.md for the milestone sequence, the remaining product work, the criteria for any
future MCP boundary, and the deliberately later AWS and Terraform capstone.
The current backend preserves the Milestone 0 worker-and-trip result and additionally:
- seeds the six-worker international-travel scenario;
- validates the policy's structured rules into a typed travel-policy model;
- applies deterministic worker, destination, effective-date, booking, and training rules;
- traces affected workers to managers, teams, systems, documents, training, and customer commitments through explicit PostgreSQL relationships;
- returns 18 categorized enterprise impacts with stable reason codes, evidence, and ordered relationship paths;
- persists the complete immutable assessment aggregate in one transaction;
- returns three affected workers, three unaffected workers, six original findings, 13 unexecuted proposed actions, and eight scenario-defined unresolved questions.
The Milestone 1 assessment behavior remains unchanged.
The assessment response's eight unresolved_questions are legacy seeded scenario fixtures, not
questions generated by the policy-analysis model. Extraction findings and persisted
policy-analysis clarifications are the authoritative uncertainty workflow. The response
compatibility gap is documented in ADR-0015 and intentionally deferred rather than hidden by a
schema change in this cleanup.
The first Milestone 2 slice converts stored policy text into proposed typed rules using LangChain structured output, then validates source spans, supported constructs, business rules, and enterprise course references deterministically. The model extracts the human course name; deterministic code resolves its enterprise identifier.
Each invocation creates an append-only extraction attempt with raw output, candidate and accepted rules, validation errors, and model/provider/prompt/schema metadata. Unsupported and invalid output fails closed and never reaches the impact engine.
The second Milestone 2 slice wraps extraction in a narrow LangGraph state machine backed by authoritative PostgreSQL application records. A run either completes with one deterministic assessment, pauses on one bounded typed clarification, terminates as unsupported, or fails with a stable code. Technical extraction failures receive at most one fresh append-only attempt.
LangGraph provides declarative topology, typed orchestration state, conditional routing, and explicit node and transition boundaries. PostgreSQL provides authoritative durability, lifecycle state, clarification state, persisted artifacts, and recovery across processes and restarts. ChangeOps does not configure a LangGraph checkpointer. Resume is a fresh graph invocation whose route is derived deterministically from persisted database state.
Clarification is deterministic and deliberately narrow. Because the existing schema-v1 assessment
rule is Literal[True], the workflow asks for an explicit true acknowledgement only when
conflicting pre-effective-date booking behavior could change the rule and assessment.
Malformed JSON is retried, not sent to a human; unsupported families and unresolved enterprise
course references terminate without blind retries.
Only a persisted extraction attempt with validation_outcome = accepted, resolved typed rules,
and no pending clarification can cross the assessment adapter. Human answers retain their own
clarification ID, responder, timestamp, and affected field; no source span is fabricated.
policy_changes.structured_rules is never changed.
GET /api/v1/policy-analysis-entry supplies the compact golden-scenario entry view.
GET /api/v1/policy-analysis-runs/{run_id}/journey assembles a read-only lineage projection from
the authoritative policy, current extraction attempt, extraction findings, clarification records,
assessment, coverage, change plan, and approval reference.
The projection deliberately omits the legacy schema-v1 assessment unresolved_questions. Those
eight copied seed fixtures remain available through the historical assessment API, but are not
presented as model-discovered uncertainty. Enterprise coverage is derived on read from the active
organization universe and persisted impact source keys; cleared objects do not receive empty
impact rows. The journey can submit the existing typed clarification, create or retrieve a
grounded interpretation, and create or open the existing approval workbench without duplicating
those workflows.
The landing and analysis pages present that lifecycle as a guided reviewer experience. Plain-language questions lead with the persisted policy, accepted extraction, deterministic assessment, proposed actions, and backend-reported workflow state. Source spans, identifiers, reason codes, relationship paths, and lineage remain available in native disclosures. This presentation does not derive eligibility, translate reason codes, or maintain a separate frontend next-action model.
The third Milestone 2 slice interprets only the persisted artifacts of a completed policy-analysis assessment. A structured model may propose review concerns about unsupported concepts, missing evidence, or incomplete mapping coverage. Its output cites only exact policy quotes, existing impact IDs, and existing evidence keys. Application code deterministically adds policy and assessment IDs, character offsets, and evidence owners before pure validation resolves every reference and accepts a separate immutable change plan.
Interpretation is non-authoritative: it cannot add, remove, reclassify, or edit deterministic impacts, evidence, reason codes, paths, actions, or counts. Provider and grounding failures create append-only failed attempts and never change the completed run or assessment. Creation is idempotent; PostgreSQL permits at most one accepted change plan per assessment while failed attempts remain available for audit and retry. Returning an existing plan does not construct or require a configured interpretation provider.
Each persisted proposed action can have one idempotently created review. The review snapshots the
original unexecuted action and relevant evidence keys without updating the assessment or proposed
action. An authorized reviewer or admin can submit exactly one approval, rejection, deferral, or
revision request with rationale.
Approval may store a separate edited description and/or due date. The API deterministically
derives the effective approved action while preserving the original snapshot. PostgreSQL triggers
make decisions append-only and terminal reviews immutable; row locks and uniqueness constraints
provide a single winner under concurrent creation or decision attempts. A review decision never
executes an action by itself, and every proposed action remains an immutable not_executed
assessment artifact.
A completed policy-analysis assessment can now create one idempotent ActionApprovalRun. The run
creates or reuses one review for each proposed action, stores explicit ordered membership, derives
counts from persisted review states, and records append-only workflow transitions. It is separate
from the already-completed PolicyAnalysisRun; approval never reopens analysis or mutates its
immutable assessment.
The narrow deterministic LangGraph invocation ends as soon as the run persists
awaiting_decisions / await_decisions. Each authorized decision commits first, then synchronously
resumes the same run. The graph recalculates all counts under a run row lock and either pauses again
or completes when every item is terminal. Approval, rejection, deferral, and revision request all
close an item; the decision mix is summarized without treating non-approval as a technical failure.
Unexpected automatic-resume failure cannot roll back a committed human decision. Authorized reviewers and admins can use the idempotent explicit resume endpoint to reconcile persisted state. Completed runs and no-op waiting resumes remain unchanged. Approval completion performs no execution; the separate controlled-execution boundary below requires another explicit request.
An authorized demonstration admin can prepare commands after an approval run completes.
Preparation considers approved reviews only, reuses the existing effective-action overlay, and
maps supported action semantics through a pure deterministic domain function. The current golden
mapping is deliberately narrow:
training_assignment / worker
→ learning / assign_training
Manager approval, team review, system-workflow review, document, and customer-commitment
recommendations remain visible as unsupported approved actions. Each supported review owns at most
one immutable command with separate effective-approved-action-v1 and execution-command-v1
snapshots. A SHA-256 idempotency key covers the approval decision and canonical command semantics.
Row locking and PostgreSQL uniqueness make repeated and concurrent preparation safe.
Every command is linked to its approval run, review, approval decision, proposed action, and
assessment. Commands remain immutable pending_execution authorization artifacts even after an
attempt; execution history is stored separately.
An authorized demonstration admin may explicitly execute a prepared command:
POST /api/v1/execution-commands/{command_id}/execute
The synchronous execution service locks the command, reconstructs and verifies its authoritative
approval lineage, validates its closed payload, and calls one explicit in-process simulated
learning adapter. A successful transaction commits both simulated_learning_assignments and
execution_results. A unique command ownership constraint prevents duplicate assignments; each
replay gets its own immutable already_applied result referencing the original assignment.
The workbench shows the worker, course, approval lineage, execution control, assignment, and audit
history. Proposed actions and approval history remain unchanged.
The proposed policy's primary source-document remediation maps through a second closed route:
operational_remediation / enterprise_document
→ jira / create_issue
Preparation requires one immutable comparison whose impact delta names the approved assessment as the proposed side. It snapshots a human-readable Task summary and Atlassian Document Format description containing the business summary, semantic policy differences, operational impact, deterministic reason, evidence, command/comparison IDs, and assessment/extraction lineage. AI does not participate.
Jira creation uses an at-most-once PostgreSQL delivery gate. Confirmed success persists one
immutable Jira receipt and replay returns already_applied without another network call.
Authentication and request-validation responses are definitive no-side-effect failures and may be
retried explicitly. Timeouts, connection loss, and 5xx responses are treated as ambiguous and are
never resent automatically, because Jira Create Issue has no unique client idempotency key and this
milestone excludes search and reconciliation. This intentionally prioritizes no duplicate issues.
The guided approval view leads with the persisted review count, current approval state, and the explicit boundary that a decision does not execute an action. Each item presents its proposal, persisted deterministic explanation, evidence, and human decision separately; evidence, reason codes, identifiers, and lineage remain available in disclosures. After approval completes, execution appears as a distinct section that renders authoritative preparation, command, result, manual-follow-up, and replay fields without deriving eligibility or transitions in the frontend. Approved actions without an automated execution mapping are described as requiring manual follow-up, while their authoritative unsupported classification and persisted reason remain available in technical details.
POST /api/v1/policy-comparisons accepts only baseline and proposed policy identifiers; reviewer
identity comes from X-ChangeOps-Actor. Application code resolves the latest completed analysis
for each source, verifies accepted extraction ownership, source snapshots, family, schema,
readiness, and provenance, then compares the explicit InternationalTravelPolicyRules fields.
No model or LangGraph workflow participates.
The comparison aggregate and its ordered differences are immutable PostgreSQL records. Repeated requests over the same accepted source attempts reuse the same SHA-256-fingerprinted comparison. The seeded proposed revision changes the effective date, removes contractor coverage, and adds Mexico to excluded destinations. Wording or source-span changes with identical accepted semantics produce no rule difference.
Every newly created or idempotently reused policy comparison owns one separate immutable enterprise impact delta between the two completed policy-analysis assessments. Pure domain code matches stable worker-and-trip, finding, and enterprise-impact business identities. Database UUIDs are retained only for lineage and never participate in matching or fingerprinting.
The delta compares two authoritative persisted assessment outcomes. It does not prove that policy changes were the sole cause of every outcome difference if enterprise source facts differed between the assessment runs. ChangeOps exposes the persisted evidence on each side but does not infer causation beyond those records. The seeded demonstration deliberately runs both assessments against the same enterprise catalog state; Milestone 5B does not add generalized enterprise snapshot versioning or catalog-state comparison.
The delta classifies workers as became affected, no longer affected, or remained affected; findings as introduced or disappeared; and enterprise impacts as introduced or removed. Each side snapshots the authoritative assessment explanation, reason codes, evidence, and relationship path. PostgreSQL constrains comparison-to-assessment lineage, makes creation idempotent, and rejects delta or delta-item updates and deletes. AI and LangGraph do not participate.
The comparison experience presents accepted policy obligations before a compact, response-derived change story and operational summary. Worker counts are labeled as worker-trip outcomes to match the persisted comparison identity. Individual worker, finding, and enterprise-impact records are collapsed by default; each can be opened independently to inspect explanations, evidence, relationship paths, stable identity, and record lineage. The page uses the same calm reviewer hierarchy as analysis and approval: immutable authority and causal scope stay visible, business semantic values lead, and rule provenance, source spans, identifiers, fingerprints, and lineage remain available through native technical disclosures.
- Docker Desktop or another Docker Engine with Docker Compose
curlfor the manual smoke test
No host Python or PostgreSQL installation is required.
Start PostgreSQL, apply migrations, load the idempotent seed data, and start the API:
docker compose up --buildThe API is available at http://localhost:8000. Verify it in another terminal:
curl --fail http://localhost:8000/healthzExpected response:
{"status":"ok"}To exercise live extraction, copy .env.example to .env and set OPENAI_API_KEY. The default
provider/model are openai and gpt-5.6-luna; override them with
EXTRACTION_MODEL_PROVIDER and EXTRACTION_MODEL. Interpretation defaults to the same provider
and model family and can be configured independently with INTERPRETATION_MODEL_PROVIDER and
INTERPRETATION_MODEL. Provider calls default to a 120-second timeout with provider-library
retries disabled; configure these bounds with *_MODEL_TIMEOUT_SECONDS and
*_MODEL_MAX_RETRIES. The policy-analysis workflow still owns its one bounded fresh-attempt retry.
To create Jira Tasks, also configure:
JIRA_BASE_URL=https://your-domain.atlassian.net
JIRA_EMAIL=operator@example.com
JIRA_API_TOKEN=your-api-token
JIRA_PROJECT_ID_OR_KEY=your-project-id-or-key
JIRA_ISSUE_TYPE_ID=your-task-issue-type-id
JIRA_TIMEOUT_SECONDS=10The account needs Browse Projects and Create Issues permission. Configure the Enterprise Change
Management project's Task workflow so its initial status is To Do; ChangeOps does not perform
transitions. Secrets stay in .env and are never stored in commands or results. Project and issue
type identifiers are configuration rather than hard-coded source values.
The repository intentionally contains no live page ID or credential. To connect the flagship Manager Travel Approval Guide, first create or select that page in a dedicated Acme Confluence space, then configure:
CONFLUENCE_BASE_URL=https://your-domain.atlassian.net
CONFLUENCE_EMAIL=changeops-reader@example.com
CONFLUENCE_API_TOKEN=your-api-token
CONFLUENCE_TIMEOUT_SECONDS=10
CONFLUENCE_MANAGER_TRAVEL_APPROVAL_GUIDE_PAGE_ID=123456789Use a dedicated account that can only view the selected page and its space. ChangeOps uses only
Confluence Cloud REST API v2 GET /wiki/api/v2/pages/{id} and
GET /wiki/api/v2/spaces/{spaceId}. Atlassian documents read:page:confluence and
read:space:confluence as the corresponding granular OAuth scopes; the direct email/API-token
configuration used here remains constrained by the account's Confluence permissions. See the
page API,
space API, and
basic-auth guidance.
Do not grant content write, delete, search, or space-administration permissions.
After the API is running, import or refresh the configured page explicitly:
curl --fail --request POST \
http://localhost:8000/api/v1/catalog-objects/enterprise_document/document-manager-travel-approval-guide/confluence-refreshThe first successful response is success; an identical later response is already_current.
Open http://localhost:3000/catalog/enterprise_document/document-manager-travel-approval-guide
to inspect the external page ID, space, version, status, refresh state, and Open in Confluence
link. Authentication, permission, not-found, unavailable, validation, and ambiguous-response
failures have distinct stable codes. A failed refresh never overwrites imported metadata.
To create the live fictional Acme content, use this bounded manual inventory:
- Required: Manager Travel Approval Guide in a space such as Acme Travel Operations.
- Optional only if later demo needs justify them: International Travel Booking Procedures and International Travel FAQ.
- Copy the required page's numeric ID from its URL or page information into the environment variable above. Do not add it to source control.
- Run the explicit refresh and verify that the returned title, space, and page ID match the intended page before the reviewer walkthrough.
No code path creates, edits, publishes, searches, crawls, or deletes Confluence content.
The bounded Acme environment was live-validated on August 4, 2026 without committing its
credentials or page ID. The explicit refresh imported the Manager Travel Approval Guide and a
second refresh returned already_current; the external link and responsive catalog detail were
verified with zero browser console errors. The credential used for that local check reported
broader page operations than view-only access. That broader credential is an accepted limitation
for the current demo environment rather than a PR B blocker. Application code remains read-only
regardless: the adapter implements only the two GET requests above.
Stop the stack while preserving database data:
docker compose downConfigure OPENAI_API_KEY in .env, then use this repeatable golden path:
- Run
make demo-reset. This builds the stack, applies migrations, preserves and idempotently refreshes the fictional enterprise catalog, removes prior workflow history, and leaves the API and web application healthy. - Open http://localhost:3000. The landing page should show the baseline policy, proposed revision, no recent analysis runs, and both policies as not ready for comparison.
- Choose Explore enterprise knowledge. Confirm 4 documents, 3 systems, and 1 training course
appear as primary categories while 12 workers, 4 teams, and 2 customer commitments appear only
as assessment-context counts. Open Manager Travel Approval Guide and inspect its stored
Acme Knowledge source, published status, version 2, missing owner, and separate baseline and
proposed
instructs_managerdependency rows. Follow the baselineMANAGER_APPROVAL_REQUIREDreference and return to the document. If Confluence is configured and explicitly refreshed, also confirm the read-only external page identity and open the page in Confluence. Otherwise confirm the honest not-imported state. - Return to policy analysis, select the baseline policy, and choose Start new analysis.
- On the journey page, inspect the AI-proposed structured values, deterministic validation, and exact source quotes. The ordinary golden policy needs no clarification and is labeled No clarification required.
- Inspect the immutable assessment: 3 affected workers, 3 cleared workers, 6 findings, 18 enterprise impacts, and 13 deterministic proposed actions. Expand impacts to inspect persisted evidence and relationship paths.
- Choose Generate grounded interpretation. Each cited policy span is confirmed against the persisted policy snapshot; impact and evidence citations show readable labels while preserving their IDs, keys, source types, and reason codes.
- Choose Create approval run. In the workbench, approve the supported training assignments and record terminal decisions for the other recommendations. Approval completion does not mean execution.
- After the approval run completes, prepare execution commands. The workbench reports one supported Learning System mapping type; the golden data produces two training commands while other approved action types remain explicitly unsupported.
- Execute a training assignment, then execute the same command again. The first result is
succeeded; replay isalready_appliedand reuses the same durable assignment. - Inspect the workbench history and return to the analysis journey. Approval, command preparation, execution, and replay counts remain distinct.
- Return to the landing page, analyze the proposed revision, then select the baseline and proposed records under Compare policy versions.
- Create the comparison. Confirm three ordered semantic differences—effective date modified, contractor coverage removed, and Mexico exclusion added—with accepted provenance on every applicable side.
- Inspect the change summary and immutable enterprise impact delta. The golden revision shows three worker-trip outcomes no longer affected, six findings disappeared, and fourteen enterprise impacts removed. No worker-trip outcome, finding, or enterprise impact is introduced, and no worker-trip outcome remains affected. The three document impacts and policy-required course impact are unchanged and omitted from the delta. Detailed records start collapsed; open one item at a time to inspect authoritative before/after reasons, evidence, relationship paths, stable identity, and lineage.
- Open the proposed assessment's approval workbench. Approve its single
operational_remediationaction for the primary policy document and record terminal decisions for the remaining recommendations. - Prepare the immutable
jira.create_issuecommand and inspect its title, comparison ID, assessment lineage, and readable description before execution. - Execute it once to create a Task in the configured Enterprise Change Management project, then
execute it again. The replay is
already_applied, links to the same Jira issue, and performs no second Jira request.
Analysis creation uses one long-running POST and never automatically repeats that mutation. Client timestamps render in a fixed timezone to keep server and browser markup stable. If the response is interrupted after the backend persists a run, the landing UI performs one authoritative entry read and offers navigation-only recovery; its retry controls do not create a duplicate analysis.
The eight legacy assessment questions remain only in the historical schema-v1 assessment response. They are seeded scenario fixtures and are not displayed as AI-derived uncertainty in the journey.
Run all unit and PostgreSQL integration tests:
docker compose run --rm api pytestRun only the pure deterministic analyzer tests:
docker compose run --rm api pytest tests/unitRun the code-quality checks:
docker compose run --rm api ruff check src migrations tests
docker compose run --rm api ruff format --check src migrations testsRun the versioned fixture-based extraction evaluation without a live provider:
docker compose run --rm api python -m changeops.evaluation.extraction \
tests/golden/extraction/v1/dataset.jsonRun the scoped deterministic workflow scenario evaluation:
docker compose run --rm api python -m changeops.evaluation.workflow \
tests/golden/workflow/v1/dataset.jsonRun the fixture-based interpretation grounding evaluation without a live provider:
docker compose run --rm api python -m changeops.evaluation.interpretation \
tests/golden/interpretation/v1/dataset.jsonRun the deterministic approval-workflow evaluation:
docker compose run --rm api python -m changeops.evaluation.approval_workflow \
tests/golden/approval_workflow/v1/dataset.jsonThe integration suite creates and removes a dedicated changeops_test database. It does not use the development database for test assessments.
Pull requests and pushes to main run the same Compose-based lint, format, pytest, and four
offline evaluation commands in GitHub Actions. CI explicitly clears OPENAI_API_KEY, so ordinary
quality checks use fixture models and cannot inherit repository or runner provider credentials.
These required evaluations protect schemas, routing, lifecycle, grounding, provenance, and
fail-closed contracts against fixed cases. Their green result is regression evidence, not a live
provider-accuracy or production model-quality benchmark.
The separate migration check creates an empty PostgreSQL database, upgrades to the discovered
Alembic head, downgrades that head by one revision, and upgrades back to head. Repository owners
should configure branch protection to require the stable quality and migration checks before
merge.
The Live AI Smoke GitHub Actions workflow is available only through manual workflow_dispatch.
It requires the OPENAI_API_KEY repository secret and starts a fresh isolated Compose stack. The
smoke runs real extraction and interpretation for the canonical policy, verifies the deterministic
3/3 worker split, 6 findings, 18 enterprise impacts, 13 unexecuted actions, and 8 unresolved
questions, then checks accepted grounding, plan retrieval, idempotency, and assessment immutability.
This dispatch-only workflow checks current provider compatibility and one end-to-end live-model path. It is intentionally non-gating because provider calls add cost, availability dependencies, and nondeterministic output. It is not a comprehensive live model-quality benchmark; that broader evaluation remains future work.
Workflow output contains only provider/model names, elapsed time, lifecycle identifiers, terminal outcome, and a stable failure code. It does not emit provider envelopes, raw model output, authorization data, or unrestricted exception text. A successful live call is deliberately not required for ordinary offline development or pull-request CI.
Apply all pending schema migrations:
docker compose run --rm migrateReload the idempotent scenario seed:
docker compose run --rm seedReset the local reviewer demo to a clean workflow state and start the stack:
make demo-resetThis command preserves the fictional organization, people, trips, systems, documents, training, commitments, both policy sources, the dependency catalog, and successfully imported Confluence document metadata. It removes comparisons and their enterprise impact deltas, analysis runs, extraction attempts, clarifications, assessments, findings, evidence, impacts, proposed actions, interpretation attempts and plans, reviews, approval runs, commands, assignments, and execution results.
The reset fails closed unless the database is PostgreSQL, the host and database name match the recognized local Compose target, the seeded organization marker exists, and the explicit confirmation value supplied by the narrowly named Make target matches. It does not drop the database or volume and is safe to repeat. Do not adapt the target for a shared or production database.
Refresh the one configured read-only Confluence source by stable ChangeOps document ID:
POST /api/v1/catalog-objects/enterprise_document/{document_id}/confluence-refreshThe existing document-detail read includes external_source_state and the last-known-good
external_source metadata when available:
GET /api/v1/catalog-objects/enterprise_document/{document_id}Create an assessment for the seeded policy:
POST /api/v1/policy-changes/policy-international-travel-2026-09/impact-assessmentsRetrieve a persisted assessment:
GET /api/v1/impact-assessments/{assessment_id}Create a new append-only extraction attempt:
POST /api/v1/policy-changes/{policy_change_id}/extraction-attemptsRetrieve an extraction attempt, including failures:
GET /api/v1/policy-extraction-attempts/{attempt_id}Create and execute a policy-analysis run:
POST /api/v1/policy-analysis-runs
Content-Type: application/json
{"policy_change_id":"policy-international-travel-2026-09"}Policy analysis is synchronous while the configured model is running. During that call, retrieving
the run reports status = running and current_step = extract, and API logs record model-call
start, completion, elapsed time, and a sanitized error category. Use curl --show-error --verbose
when exercising a live provider so the open request is visible.
Retrieve durable state after completion or a process restart:
GET /api/v1/policy-analysis-runs/{run_id}Create or idempotently retrieve a deterministic policy comparison after both policies have completed analyses:
POST /api/v1/policy-comparisons
X-ChangeOps-Actor: reviewer@example.com
Content-Type: application/json
{
"baseline_policy_change_id": "policy-international-travel-2026-09",
"proposed_policy_change_id": "policy-international-travel-proposed-2026-10"
}Retrieve the immutable comparison:
GET /api/v1/policy-comparisons/{comparison_id}A new comparison and impact delta return 201; an equivalent repeat returns 200. Both include
Location. The response includes the immutable assessment pairing, summary, ordered worker,
finding, and enterprise-impact deltas, and authoritative side evidence. The client cannot supply
attempts, assessments, rules, differences, delta items, provenance, materiality, reason codes,
ordering, evidence, or fingerprints.
Answer the pending typed clarification and resume:
POST /api/v1/policy-analysis-runs/{run_id}/clarifications/{clarification_id}/answer
Content-Type: application/json
{"value":true,"responder_identity":"reviewer@example.com"}The persisted answer contract is
{"type":"literal","allowed_values":[true]}; false is rejected without answering or advancing
the clarification.
Create or idempotently retrieve the accepted plan for a completed workflow assessment:
POST /api/v1/impact-assessments/{assessment_id}/change-plans
GET /api/v1/impact-assessments/{assessment_id}/change-plan
GET /api/v1/change-plans/{change_plan_id}Retrieve any interpretation attempt, including invalid and provider-failed attempts:
GET /api/v1/policy-interpretation-attempts/{attempt_id}Interpretation is explicitly requested after the policy-analysis run completes. A successful new
plan returns 201; an existing plan returns 200. Failed attempts do not block a later retry.
Create or idempotently retrieve a review, then retrieve it by either stable identifier:
POST /api/v1/proposed-actions/{proposed_action_id}/review
GET /api/v1/proposed-actions/{proposed_action_id}/review
GET /api/v1/action-reviews/{review_id}Submit the one terminal decision using the demonstration authorization headers:
POST /api/v1/action-reviews/{review_id}/decisions
X-ChangeOps-Actor: reviewer@example.com
X-ChangeOps-Role: reviewer
Content-Type: application/json
{
"decision": "approved",
"rationale": "Approved with a clearer assignment description.",
"edited_action": {
"description": "Assign the updated international travel course.",
"due_date": "2026-08-20"
}
}The headers are a replaceable demonstration boundary, not production authentication. Retrieval remains unauthenticated.
Create or idempotently retrieve the approval run for a completed policy-analysis assessment:
POST /api/v1/impact-assessments/{assessment_id}/approval-run
GET /api/v1/impact-assessments/{assessment_id}/approval-run
GET /api/v1/action-approval-runs/{run_id}
GET /api/v1/action-approval-runs/{run_id}/workbenchExplicitly reconcile or resume the same run:
POST /api/v1/action-approval-runs/{run_id}/resume
X-ChangeOps-Actor: reviewer@example.com
X-ChangeOps-Role: reviewerCreation returns awaiting_decisions while any item is pending and may return completed
immediately when every reused review was already terminal. Resume returns 200 and is idempotent,
including for waiting runs with no new decision and completed runs. The demonstration headers are
not safe production authentication.
Inspect command eligibility, prepare commands, and retrieve one immutable command:
GET /api/v1/action-approval-runs/{run_id}/execution-commands
POST /api/v1/action-approval-runs/{run_id}/execution-commands
X-ChangeOps-Actor: operator@example.com
X-ChangeOps-Role: admin
GET /api/v1/execution-commands/{command_id}The POST has no action-detail request body. It returns 201 when at least one command is created
and 200 when fully idempotent. The response preserves approval membership order, includes
unsupported approved actions, and reports execution state separately from immutable command and
proposed-action state.
The workbench endpoint is a read-only, screen-oriented projection. It resolves immutable run
membership, complete review snapshots, findings, enterprise impacts, persisted evidence, and
ordered relationship paths without creating a durable workbench record. A missing reference
returns the stable approval_workbench_inconsistent error instead of silently dropping context.
The Compose seed creates stable fictional source data—including the baseline and proposed international-travel sources—but no completed workflow or comparison history. Start at http://localhost:3000, analyze either source, follow the product handoff into the approval workbench when useful, and compare the two sources after both are ready. API and OpenAPI documentation remain available at http://localhost:8000/docs.
The reviewer email field populates X-ChangeOps-Actor; decision and retry requests send
X-ChangeOps-Role: reviewer, while command preparation sends the narrowly authorized
X-ChangeOps-Role: admin. These are trusted local demonstration headers, not login or production
authentication.
The interface labels AI proposals, deterministic conclusions, human decisions, workflow state,
and execution state in text. Approval may edit only description and due date. Every write is
followed by an authoritative server refresh. Proposed actions remain immutable not_executed
assessment artifacts even when a separate command has execution results. Once the run completes,
the same page shows execution eligibility, unsupported approvals, immutable commands, an explicit
controls for the supported Learning assignment and Jira create-Task operations, durable receipts,
and immutable result history. Approval alone never invokes either control.
Create operations return 201 Created; run, extraction, and change-plan creates include a
Location header.
-
Start the complete stack in the background:
docker compose up --build -d
-
Confirm the API is healthy:
curl --fail http://localhost:8000/healthz
-
Create an assessment and inspect the response headers and body:
curl --include --request POST \ http://localhost:8000/api/v1/policy-changes/policy-international-travel-2026-09/impact-assessments
-
Copy the assessment path from the
Locationresponse header, then retrieve it:curl --fail \ http://localhost:8000/api/v1/impact-assessments/PASTE_ASSESSMENT_ID_HERE
-
Restart only the API container:
docker compose restart api
-
Repeat the retrieval command from step 4. The same completed assessment should still be returned from PostgreSQL.
-
Stop the stack without deleting the database volume:
docker compose down