Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ JIRA_API_TOKEN=
JIRA_PROJECT_ID_OR_KEY=
JIRA_ISSUE_TYPE_ID=
JIRA_TIMEOUT_SECONDS=10
CONFLUENCE_BASE_URL=
CONFLUENCE_EMAIL=
CONFLUENCE_API_TOKEN=
CONFLUENCE_TIMEOUT_SECONDS=10
CONFLUENCE_MANAGER_TRAVEL_APPROVAL_GUIDE_PAGE_ID=
85 changes: 75 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ ChangeOps analyzes operational and policy changes, identifies affected people an

## Current milestone

Milestone 7 PR A, Enterprise Knowledge Catalog Explorer.
Milestone 7 PR B, Read-Only Confluence Document Identity.

The local application now adds a read-only catalog explorer over the existing typed PostgreSQL
documents, systems, training courses, and policy dependencies. A reviewer can browse stable source
identities, inspect stored versus normalized metadata, follow policy-scoped rule references, and
see why persisted dependency rows are deterministic assessment inputs while row-level origin
provenance remains explicitly not recorded. Existing analysis, comparison, approval, Learning
System, and Jira behavior remains unchanged. PR A adds no migration or external catalog source;
Confluence remains the separately planned PR B.
The catalog explorer can now associate the selected Manager Travel Approval Guide with one real,
read-only Confluence Cloud page. An explicit refresh validates the configured page identity,
imports only bounded page and space metadata into PostgreSQL, and exposes the canonical external
link without changing the stable ChangeOps document ID. Failed refreshes preserve last-known-good
metadata. Existing relationships, assessment behavior, comparison, approval, Learning System, and
Jira behavior remain unchanged. Relationship-origin provenance remains the separately deferred
Milestone 7 PR C.

Milestone 0 is complete and preserved by the `v0.0.1-milestone-0` tag.

Expand Down Expand Up @@ -325,6 +325,55 @@ Management project's Task workflow so its initial status is **To Do**; ChangeOps
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.

### Optional read-only Confluence page

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:

```dotenv
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=123456789
```

Use 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](https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/),
[space API](https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-space/), and
[basic-auth guidance](https://developer.atlassian.com/cloud/confluence/basic-auth-for-rest-apis/).
Do not grant content write, delete, search, or space-administration permissions.

After the API is running, import or refresh the configured page explicitly:

```bash
curl --fail --request POST \
http://localhost:8000/api/v1/catalog-objects/enterprise_document/document-manager-travel-approval-guide/confluence-refresh
```

The 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:

1. Required: **Manager Travel Approval Guide** in a space such as **Acme Travel Operations**.
2. Optional only if later demo needs justify them: **International Travel Booking Procedures**
and **International Travel FAQ**.
3. 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.
4. 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.

Stop the stack while preserving database data:

```bash
Expand All @@ -345,7 +394,9 @@ Configure `OPENAI_API_KEY` in `.env`, then use this repeatable golden path:
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_manager` dependency rows. Follow the baseline
`MANAGER_APPROVAL_REQUIRED` reference and return to the document.
`MANAGER_APPROVAL_REQUIRED` reference 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.
4. Return to policy analysis, select the baseline policy, and choose **Start new analysis**.
5. 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
Expand Down Expand Up @@ -497,7 +548,8 @@ make demo-reset
```

This command preserves the fictional organization, people, trips, systems, documents, training,
commitments, both policy sources, and the dependency catalog. It removes comparisons and their
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
Expand All @@ -511,6 +563,19 @@ database.

## API

Refresh the one configured read-only Confluence source by stable ChangeOps document ID:

```http
POST /api/v1/catalog-objects/enterprise_document/{document_id}/confluence-refresh
```

The existing document-detail read includes `external_source_state` and the last-known-good
`external_source` metadata when available:

```http
GET /api/v1/catalog-objects/enterprise_document/{document_id}
```

Create an assessment for the seeded policy:

```http
Expand Down
5 changes: 5 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ services:
JIRA_PROJECT_ID_OR_KEY: ${JIRA_PROJECT_ID_OR_KEY:-}
JIRA_ISSUE_TYPE_ID: ${JIRA_ISSUE_TYPE_ID:-}
JIRA_TIMEOUT_SECONDS: ${JIRA_TIMEOUT_SECONDS:-10}
CONFLUENCE_BASE_URL: ${CONFLUENCE_BASE_URL:-}
CONFLUENCE_EMAIL: ${CONFLUENCE_EMAIL:-}
CONFLUENCE_API_TOKEN: ${CONFLUENCE_API_TOKEN:-}
CONFLUENCE_TIMEOUT_SECONDS: ${CONFLUENCE_TIMEOUT_SECONDS:-10}
CONFLUENCE_MANAGER_TRAVEL_APPROVAL_GUIDE_PAGE_ID: ${CONFLUENCE_MANAGER_TRAVEL_APPROVAL_GUIDE_PAGE_ID:-}
ports:
- "${API_PORT:-8000}:8000"
healthcheck:
Expand Down
60 changes: 52 additions & 8 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# ChangeOps Architecture

This document describes the current implementation through Milestone 7 PR A: the completed
This document describes the current implementation through Milestone 7 PR B: the completed
policy-analysis and approval lifecycles, the integrated Next.js journey and workbench,
deterministic preparation and explicit execution of learning-assignment and Jira create-issue
commands, and immutable
deterministic comparison of two accepted international-travel policy rulesets and their immutable
enterprise impact assessments, plus the read-only Enterprise Knowledge Catalog Explorer over the
existing typed source and dependency tables.
enterprise impact assessments, plus the read-only Enterprise Knowledge Catalog Explorer and its
narrow persisted Confluence Cloud document-identity boundary.

## High-level architecture

Expand Down Expand Up @@ -50,6 +50,8 @@ flowchart LR
ImpactDelta["Enterprise impact delta service"]
DeltaComparator["Pure stable-identity delta comparator"]
CatalogProjection["Read-only catalog projection"]
ConfluenceRefresh["Explicit Confluence metadata refresh"]
ConfluenceAdapter["Read-only Confluence Cloud adapter"]
ORM["SQLAlchemy models and sessions"]
end

Expand Down Expand Up @@ -88,6 +90,10 @@ flowchart LR
CommandPreparation --> ORM
Routes --> ExecutionService
Routes --> CatalogProjection
Routes --> ConfluenceRefresh
ConfluenceRefresh --> ConfluenceAdapter
ConfluenceRefresh --> ORM
ConfluenceAdapter --> ConfluenceCloud["Confluence Cloud"]
Routes --> Comparison
Comparison --> Comparator
Comparison --> ImpactDelta
Expand All @@ -111,7 +117,8 @@ assessment, comparison, interpretation, review, approval-run, command, simulated
learning-assignment, and execution-result data. The Next.js runtime provides the local analysis,
comparison, approval, and enterprise-catalog experiences. A configured
chat-model provider supports extraction and interpretation. Jira Cloud is the only real external
side-effect target; command preparation and simulated Learning execution remain local.
side-effect target. Confluence Cloud is a read-only external metadata source; command preparation
and simulated Learning execution remain local.

## Major runtime components

Expand Down Expand Up @@ -150,6 +157,7 @@ The FastAPI application exposes:
- `POST /api/v1/execution-commands/{command_id}/execute`
- `GET /api/v1/catalog-objects?organization_id={organization_id}`
- `GET /api/v1/catalog-objects/{object_type}/{object_id}`
- `POST /api/v1/catalog-objects/enterprise_document/{document_id}/confluence-refresh`
- `GET /api/v1/policy-changes/{policy_change_id}/rule-references/{rule_code}`

The create response adds an input fingerprint, enterprise-impact summary, and categorized
Expand Down Expand Up @@ -829,7 +837,7 @@ into the reviewer application because a pre-completed run obscures the start of

`make demo-reset` applies migrations, runs the idempotent catalog seed, then truncates the explicit
set of delta-, comparison-, and workflow-owned tables in one transaction. It preserves both policy sources
and other catalog tables and requires an exact
and other catalog tables, including imported `confluence_document_sources`, and requires an exact
confirmation value, recognized local PostgreSQL host and database name, and the seeded organization
marker. No database or volume is dropped.

Expand All @@ -852,8 +860,39 @@ assessment-context counts.

Next.js server-renders `/catalog`, supported object details, and policy-rule-reference details with
uncached API reads. Native `details` elements keep raw IDs and integrity basis collapsed by default.
There is no search, administration, relationship editing, graph view, Confluence access, or new
runtime dependency. PR A adds no migration and does not change assessment behavior.
There is no search, administration, relationship editing, or graph view. PR A added no migration
and PR B does not change assessment behavior.

## Read-only Confluence document identity boundary

`confluence_document_sources` is a Confluence-specific one-to-one child of
`enterprise_documents`; the existing string document primary key remains the catalog and
assessment identity. The child stores only validated external page metadata: provider, page ID
and title, canonical URL, space ID/key, version, status, source-updated time, import/refresh times,
source fingerprint, and the latest safe refresh result. It is not a catalog registry and contains
no policy relationships or arbitrary metadata JSON.

Only `document-manager-travel-approval-guide` has an explicit environment mapping. The refresh
request accepts its stable ChangeOps ID, resolves the configured page ID server-side, calls
Confluence REST API v2 for that page and its owning space, validates both identities, and computes
a canonical SHA-256 fingerprint. The first valid response inserts the child row. A matching
fingerprint returns `already_current`; changed external metadata updates only the child row.

Provider calls happen outside a database transaction. Persistence then locks the parent document
before insert/update so concurrent successful refreshes cannot create duplicate identities.
Authentication, permission, page-not-found, unavailable, validation, and ambiguous-response
failures are distinct. If a last-known-good row exists, failure updates only its latest outcome
fields; imported metadata and `refreshed_at` remain unchanged. A first-ever failure creates no
unvalidated external identity.

The catalog detail response projects imported metadata next to—never over—the existing ChangeOps
document title, version, status, and stable ID. Relationships still come only from typed policy
dependency tables, and the assessment loader never queries `confluence_document_sources`.
Confluence cannot create relationships, findings, impacts, actions, approvals, or execution.

The adapter uses only standard-library HTTPS GETs with a bounded timeout. Credentials and the one
page ID are environment-only. Automated tests use captured Confluence page/space fixtures and
mocked network behavior; CI requires no Atlassian account.

## Policy-analysis journey read model

Expand Down Expand Up @@ -897,10 +936,15 @@ Jira Cloud is the only real side-effect target. Its synchronous REST v3 adapter
credentials and configured project/Task identifiers. Automated tests inject or mock it; CI needs
no Jira account or secret.

Confluence Cloud is a read-only source. Its synchronous REST v2 adapter reads one configured page
and that page's space using environment credentials. It never sends a write method, searches,
crawls, polls, or changes ChangeOps relationships. Automated tests inject or mock it; CI needs no
Confluence account or secret.

There are no:

- agents, tool-calling loops, embeddings, RAG, or vector-search components;
- MCP or additional live enterprise integrations;
- MCP or additional live enterprise integrations beyond the bounded Jira and Confluence adapters;
- graph databases;
- background workers or message queues;
- Jira operations beyond create Task, including transitions and reconciliation;
Expand Down
54 changes: 54 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,3 +790,57 @@ search, transition, update, or reconciliation path is added.
- The system prefers missed automatic recovery over violating the no-duplicate requirement.
- Jira remains an execution adapter and does not enter analysis, comparison, approval, or planning
domain models.

# ADR-0020 — Preserve ChangeOps Document Identity Beside a Narrow Confluence Source

## Status

Accepted

## Context

Milestone 7 PR B must prove that one ChangeOps enterprise document corresponds to a real
Confluence Cloud page. The existing `enterprise_documents.id` is already referenced by typed
policy dependencies, assessment fingerprints, evidence, impact paths, comparisons, and proposed
actions. Replacing it with a provider identifier would make an external knowledge system
authoritative for ChangeOps analysis and destabilize historical lineage.

The repository has no configured live page. Automated verification must therefore use captured
provider metadata and must not commit a placeholder page ID. The slice needs explicit refresh,
safe failure behavior, and a visible external link, but no crawling, content ingestion, editing,
relationship creation, or multi-provider platform.

## Decision

Add one Confluence-specific `confluence_document_sources` table with a one-to-one foreign key to
`enterprise_documents`. It stores only validated page identity and metadata, import/refresh times,
a canonical source fingerprint, and the latest safe refresh outcome. It stores neither policy
relationships nor arbitrary provider JSON.

Only the Manager Travel Approval Guide has a named environment page-ID mapping. A manual POST by
stable ChangeOps document ID performs two read-only Confluence REST API v2 calls: get the page and
get its owning space. The service validates the configured and returned identities before an
idempotent insert/update. It updates no column on `enterprise_documents`.

A first failed refresh persists no unvalidated identity. After a successful import, a later
failure updates only outcome fields and retains all last-known-good metadata. The catalog detail
projects both identities side by side. The assessment and relationship services never query the
Confluence table.

The implementation uses direct email/API-token authentication to match the existing bounded Jira
configuration pattern. The configured account must have view-only access to the selected page and
space. OAuth scope guidance for an equivalent app is limited to `read:page:confluence` and
`read:space:confluence`.

## Consequences

- ChangeOps document, relationship, assessment, and audit identities remain stable if a page is
renamed, versioned, unavailable, or later moved between spaces.
- Confluence remains authoritative only for the imported external page metadata.
- Fixture-backed CI is deterministic and needs no live Atlassian credential.
- Adding another document requires an explicit named configuration decision; arbitrary page
selection, search, crawling, and bulk synchronization are unavailable.
- Retargeting a successfully imported document to a different page is rejected rather than
silently rewriting external identity.
- Relationship-origin provenance is still absent. PR C remains a separate schema and governance
decision.
25 changes: 25 additions & 0 deletions docs/demo-scenario.md
Original file line number Diff line number Diff line change
Expand Up @@ -943,3 +943,28 @@ the immutable success result. Repeating execution returns `already_applied`, ref
receipt, and performs no second Jira request. A mocked ambiguous availability failure demonstrates
the at-most-once safety rule: ChangeOps records the unavailable outcome and refuses to resend when
the first delivery may have succeeded.

## Milestone 7 PR B Read-Only Confluence Identity Extension

The Manager Travel Approval Guide retains the stable ChangeOps identity
`document-manager-travel-approval-guide`. When a reviewer has manually configured a real Acme
Confluence page and explicitly refreshed it, the document detail additionally shows:

- source provider `Confluence Cloud`;
- the validated external page ID;
- owning space key and ID;
- Confluence page version and current/draft/archived status;
- source-updated and last-refreshed timestamps;
- an **Open in Confluence** link.

The repository and automated demo remain honest when no live page exists: the detail shows that
Confluence metadata has not been imported, while fixture-backed tests prove the full boundary.
`make demo-reset` preserves a successful import. A failed later refresh leaves the last-known-good
metadata visible with the precise failure state.

Confluence is authoritative only for the imported page metadata. ChangeOps remains authoritative
for its internal document identity, curated typed policy relationships, assessment behavior,
workflow state, and audit lineage. The refresh does not change the Manager guide's two
`instructs_manager` rows or any golden assessment fingerprint/count. No Confluence content,
relationship origin, crawling, RAG, editing, publishing, background synchronization, or PR C
provenance is included.
Loading
Loading