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
8 changes: 7 additions & 1 deletion .github/workflows/repository-metadata-reconcile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- "tests/test_repository_metadata_convergence.py"
- "tests/test_repository_metadata_identity.py"
- "tests/test_repository_metadata_live_verification.py"
- "tests/test_repository_metadata_workflow.py"
- "tests/test_repository_metadata_workflow_pages.py"
- "tests/test_repository_label_taxonomy.py"
- "tests/test_repository_label_reconciliation.py"
Expand Down Expand Up @@ -122,9 +123,14 @@ jobs:
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Require dedicated repository settings credential
env:
GH_TOKEN: ${{ secrets.CWL_REPOSITORY_METADATA_TOKEN }}
shell: bash
run: test -n "${GH_TOKEN}"
- name: Reconcile and verify repository public surfaces
env:
GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}
GH_TOKEN: ${{ secrets.CWL_REPOSITORY_METADATA_TOKEN }}
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
run: |
set +e
python scripts/ci/reconcile_repository_metadata.py \
Expand Down
5 changes: 3 additions & 2 deletions docs/adr/0020-repository-public-surface-reconciliation.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The organization therefore needs one auditable owner for the desired state and o
5. DeepWiki README content is not mutated centrally. `deepwiki: true` requires the exact linked badge on the default branch before metadata writes; `deepwiki: false` fails closed while that exact badge is still present so desired state cannot silently contradict the public README.
6. Pages has two explicit ownership modes. Legacy mode requires the repository default branch to contain the regular file `docs/index.md`; absent legacy sites may be created at `/docs`, drifted legacy sites may be updated, and converged sites receive no write. Workflow mode requires the regular file `.github/workflows/pages.yml` on the protected default branch **and** an already-existing live Pages configuration with `build_type: workflow`. The central reconciler never creates or converts a workflow-backed site. Those workflow-mode source and live-configuration preconditions are validated before description, topic, or Pages mutation so an invalid workflow declaration cannot leave a partially applied metadata record.
7. Contents API source probes are type-aware. A successful response satisfies a required-source precondition only when the response is a single object with `type: file`; a directory object or directory listing is not accepted as reviewed file evidence.
8. Pull-request execution is read-only validation. Privileged reconciliation runs only from trusted `.github/main`, uses the existing maintainer credential, does not widen pull-request tokens, and does not bypass repository rulesets or reviews.
8. Pull-request execution is read-only validation. Privileged reconciliation runs only from trusted `.github/main` and obtains write authority only from the protected `repository-metadata-maintenance` environment's dedicated `CWL_REPOSITORY_METADATA_TOKEN`. The apply job fails before either mutation lane starts when that credential is absent. It must not fall back to `PR_REVIEW_MERGE_TOKEN`, reviewer/model/provider credentials, or a widened pull-request `GITHUB_TOKEN`, and it does not bypass repository rulesets or reviews.
9. Reconciliation runs from the trusted hourly schedule and exposes no branch-selectable `workflow_dispatch` entrypoint. Pull-request validation keeps a PR-stable concurrency lineage and cancels superseded validation runs; trusted scheduled protected-main apply remains non-cancellable so a replacement heartbeat cannot abandon a partially updated fleet.
10. Metadata and label lanes retain independent exit statuses during apply: label reconciliation still runs after an aggregated metadata failure, and the job fails afterward if either lane failed.
11. Repository-wide tests, focused 100% statement/branch coverage for both reconciliation scripts, docstring gates, manifest/taxonomy validation, and `git diff --check` are required before apply can run.
Expand All @@ -32,11 +32,12 @@ The organization therefore needs one auditable owner for the desired state and o
- Actions-backed Pages can be enrolled without silently rewriting a repository's reviewed deployment architecture to legacy `/docs`.
- Workflow-mode failure is fail-before-write for the repository record: missing workflow source, missing Pages, or a non-workflow live build type prevents description/topic mutation as well as Pages mutation.
- Explicit label assignments intentionally favor evidence over broad title heuristics. Expanding classification coverage requires a reviewed assignment or a separately justified deterministic classifier.
- The privileged token must retain only the repository-administration/Pages/issue permissions required by the declared fleet. Credential values never enter the manifest or logs.
- `CWL_REPOSITORY_METADATA_TOKEN` is a distinct least-privilege settings identity. It must retain only the repository-administration/Pages/issue permissions required by the declared fleet, remain unavailable to pull-request code and model processes, and never enter the manifest, logs, or artifacts. Removing it makes protected-main apply fail closed while read-only PR validation remains usable.

## Rejected alternatives

- **Report missing connector mutations without repair.** Rejected because the organization owns a GitHub Actions/API control plane that can safely provide the capability.
- **Reuse `PR_REVIEW_MERGE_TOKEN` for repository settings writes.** Rejected because merge/review authority and organization-wide repository-settings authority are separate security capabilities; coupling them unnecessarily broadens blast radius and makes least-privilege revocation impossible.
- **Mutate README badges from the central control plane.** Rejected because that would bypass the active product writer and make customer-facing content independent of product review.
- **Convert workflow-backed Pages to legacy `/docs` for uniformity.** Rejected because deployment ownership is a reviewed product boundary; reconciliation must preserve an explicitly declared Actions-backed deployment rather than rewrite it.
- **Treat any successful Contents API response as file evidence.** Rejected because a directory can exist at the same path and must not satisfy a regular-file precondition.
Expand Down
6 changes: 3 additions & 3 deletions docs/doctoring/repository-public-surface-reconciliation.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The fleet loop is deliberately non-blocking. Every repository or label assignmen

- Pull-request validation has `contents: read` only. It cannot mutate repository settings or labels.
- Apply runs only when the scheduled workflow is executing from trusted `refs/heads/main` after validation.
- The apply step uses the established maintainer credential rather than widening the ordinary workflow token. PR #1625 owns the reviewed migration to the dedicated `CWL_REPOSITORY_METADATA_TOKEN`; until that reaches protected main, no documentation may claim the dedicated credential is live.
- Apply obtains repository-settings write authority only from the protected `repository-metadata-maintenance` environment's dedicated `CWL_REPOSITORY_METADATA_TOKEN`. The job fails before either mutation lane starts when that credential is absent and never falls back to `PR_REVIEW_MERGE_TOKEN`, reviewer/model/provider credentials, or a widened pull-request `GITHUB_TOKEN`. External provisioning remains owned by issue #1579; source integration alone does not prove the secret exists.
- Repository README changes remain leaf-owned. The central reconciler verifies exact DeepWiki linkage but never fabricates or silently edits customer-facing README copy.
- Pages has two reviewed deployment modes. Legacy mode requires a regular `docs/index.md` file on the live default branch. Explicit `pages_mode: workflow` requires a regular `.github/workflows/pages.yml` file **and** an already-configured live Pages site whose `build_type` is `workflow`.
- Workflow mode is preserve-only: the reconciler does not create or convert the Pages configuration. Missing Pages, a legacy live configuration, a directory at the required workflow path, or a missing workflow file fails before description/topic/Page writes for that repository.
Expand All @@ -62,7 +62,7 @@ The newest cohort has explicit source ownership: `ContextualWisdomLab/PolicyWeav

An Actions-backed repository is not enrolled merely because `pages_mode: workflow` is supported. Enrollment requires an explicit reviewed manifest change after the repository's standard Pages workflow and live `build_type: workflow` configuration both exist. This preserves the deployment architecture of repositories such as ScopeWeave instead of silently rewriting them to legacy `/docs`.

The explicit label assignments now cover 39 active evidence-backed targets: `ContextualWisdomLab/.github#1582`, `ContextualWisdomLab/.github#1622`, `ContextualWisdomLab/.github#1625`, `ContextualWisdomLab/.github#1634`, `ContextualWisdomLab/CalendarWeave#1`, `ContextualWisdomLab/ConceptWeave#1`, `ContextualWisdomLab/context-graph-contracts#20`, `ContextualWisdomLab/RankWeave#40`, `ContextualWisdomLab/fast-mlsirm#1717`, `ContextualWisdomLab/EgressWeave#231`, `ContextualWisdomLab/psychometrics-commons#442`, `ContextualWisdomLab/contextual-orchestrator#994`, `ContextualWisdomLab/contextual-orchestrator#1003`, `ContextualWisdomLab/appguardrail#1077`, `ContextualWisdomLab/naruon#1513`, `ContextualWisdomLab/LineageWeave#908`, `ContextualWisdomLab/ContextualWisdomLab.github.io#203`, `ContextualWisdomLab/TEPP#435`, `ContextualWisdomLab/semantic-data-portal#72`, `ContextualWisdomLab/Orgmetra#160`, `ContextualWisdomLab/learning-interoperability-contracts#1`, `ContextualWisdomLab/noema#530`, `ContextualWisdomLab/bandscope#1125`, `ContextualWisdomLab/saju-caldav#44`, `ContextualWisdomLab/OriginWeave#274`, `ContextualWisdomLab/semantic-data-portal#90`, `ContextualWisdomLab/accounting-information-platform#45`, `ContextualWisdomLab/clearfolio#538`, `ContextualWisdomLab/pg-erd-cloud#1046`, `ContextualWisdomLab/DiagramWeave#34`, `ContextualWisdomLab/keyverse#127`, `ContextualWisdomLab/mhtml-etl-gateway#56`, `ContextualWisdomLab/j-planner#2`, `ContextualWisdomLab/learning-record-store#1`, `ContextualWisdomLab/learning-content-studio#1`, `ContextualWisdomLab/learning-management-platform#1`, `ContextualWisdomLab/metering-billing-platform#157`, `ContextualWisdomLab/PolicyWeave#1`, and `ContextualWisdomLab/supply-chain-control-plane#1`. Closed superseded child PRs `learning-record-store#7`, `learning-content-studio#8`, and `metering-billing-platform#175` are deliberately absent from the active reconciliation target list because their unique documentation deltas were folded into their authoritative parent writers. Historical labels on those closed PRs are not erased by this desired-state change. The assignment reconciler preserves richer repository-local labels such as priority, status, and `type: maintenance` when those labels are outside the managed semantic set.
The explicit label assignments cover 39 active evidence-backed targets: `ContextualWisdomLab/.github#1582`, `ContextualWisdomLab/.github#1622`, `ContextualWisdomLab/.github#1625`, `ContextualWisdomLab/.github#1634`, `ContextualWisdomLab/CalendarWeave#1`, `ContextualWisdomLab/ConceptWeave#1`, `ContextualWisdomLab/context-graph-contracts#20`, `ContextualWisdomLab/RankWeave#40`, `ContextualWisdomLab/fast-mlsirm#1717`, `ContextualWisdomLab/EgressWeave#231`, `ContextualWisdomLab/psychometrics-commons#442`, `ContextualWisdomLab/contextual-orchestrator#994`, `ContextualWisdomLab/contextual-orchestrator#1003`, `ContextualWisdomLab/appguardrail#1077`, `ContextualWisdomLab/naruon#1513`, `ContextualWisdomLab/LineageWeave#908`, `ContextualWisdomLab/ContextualWisdomLab.github.io#203`, `ContextualWisdomLab/TEPP#435`, `ContextualWisdomLab/semantic-data-portal#72`, `ContextualWisdomLab/Orgmetra#160`, `ContextualWisdomLab/learning-interoperability-contracts#1`, `ContextualWisdomLab/noema#530`, `ContextualWisdomLab/bandscope#1125`, `ContextualWisdomLab/saju-caldav#44`, `ContextualWisdomLab/OriginWeave#274`, `ContextualWisdomLab/semantic-data-portal#90`, `ContextualWisdomLab/accounting-information-platform#45`, `ContextualWisdomLab/clearfolio#538`, `ContextualWisdomLab/pg-erd-cloud#1046`, `ContextualWisdomLab/DiagramWeave#34`, `ContextualWisdomLab/keyverse#127`, `ContextualWisdomLab/mhtml-etl-gateway#56`, `ContextualWisdomLab/j-planner#2`, `ContextualWisdomLab/learning-record-store#1`, `ContextualWisdomLab/learning-content-studio#1`, `ContextualWisdomLab/learning-management-platform#1`, `ContextualWisdomLab/metering-billing-platform#157`, `ContextualWisdomLab/PolicyWeave#1`, and `ContextualWisdomLab/supply-chain-control-plane#1`. Closed superseded child PRs `learning-record-store#7`, `learning-content-studio#8`, and `metering-billing-platform#175` are deliberately absent from the active reconciliation target list because their unique documentation deltas were folded into their authoritative parent writers. Historical labels on those closed PRs are not erased by this desired-state change. The assignment reconciler preserves richer repository-local labels such as priority, status, and `type: maintenance` when those labels are outside the managed semantic set.

## Verification contract

Expand Down Expand Up @@ -90,4 +90,4 @@ GitHub's current REST Pages contract supports `build_type` values `legacy` and `

## Known integration boundary

The 22-repository desired-state extension is now protected on `.github/main@611feef038ad52d7ee1214d03ea3527289ebf711`; this label-taxonomy lane is the remaining central source delta in the metadata stack. Live settings convergence still requires repository-local badge/Pages prerequisites, trusted protected-main apply, and postcondition re-reads. The dedicated settings-credential migration remains owned by #1625 and is not claimed live before that PR lands. These integration states are not reasons to idle the fleet: blocked leaves fail closed while eligible siblings, labels, and independent public-surface work continue.
The 22-repository desired state and 39-target label taxonomy are protected on `.github/main@ad65125acfe901bf4c4958b6c705ffce17714358`. This lane changes only the credential boundary and its durable contracts. After source integration, issue #1579 remains open until the dedicated GitHub App/token is actually provisioned in the protected environment, a trusted-main reconciliation run obtains it without disclosure, and a live canary re-read proves the intended repository settings. Source integration is therefore necessary but not sufficient evidence of live convergence.
18 changes: 18 additions & 0 deletions tests/test_repository_metadata_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Static contracts for the privileged repository metadata workflow."""

from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
WORKFLOW = ROOT / ".github" / "workflows" / "repository-metadata-reconcile.yml"


def test_metadata_apply_uses_dedicated_least_privilege_credential() -> None:
"""Repository settings writes must not reuse the review/merge credential."""
source = WORKFLOW.read_text(encoding="utf-8")

assert "secrets.CWL_REPOSITORY_METADATA_TOKEN" in source
apply_source = source.split(" apply:", 1)[1]
assert "secrets.PR_REVIEW_MERGE_TOKEN" not in apply_source
assert "Require dedicated repository settings credential" in apply_source
assert 'test -n "${GH_TOKEN}"' in apply_source
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
Loading