From 46515bce6597c5f7e9f50f1841aeb960e92d52e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 22:17:37 +0900 Subject: [PATCH 01/17] ci: add hourly DB-grounded commercialization loop --- .../hourly-commercialization-loop.yml | 917 ++++++++++++++++++ ...urly-db-grounded-commercialization-loop.md | 20 + docs/doctoring/PRODUCT_UX_REFERENCES.md | 93 ++ .../hourly-commercialization-loop.md | 226 +++++ ...026-08-14-db-grounded-product-ux-design.md | 402 ++++++++ .../test_hourly_commercialization_workflow.py | 217 +++++ 6 files changed, 1875 insertions(+) create mode 100644 .github/workflows/hourly-commercialization-loop.yml create mode 100644 CHANGELOG.d/hourly-db-grounded-commercialization-loop.md create mode 100644 docs/doctoring/PRODUCT_UX_REFERENCES.md create mode 100644 docs/operations/hourly-commercialization-loop.md create mode 100644 docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md create mode 100644 tests/test_hourly_commercialization_workflow.py diff --git a/.github/workflows/hourly-commercialization-loop.yml b/.github/workflows/hourly-commercialization-loop.yml new file mode 100644 index 000000000..9fce2e9b8 --- /dev/null +++ b/.github/workflows/hourly-commercialization-loop.yml @@ -0,0 +1,917 @@ +name: Hourly LineageWeave Commercialization Loop + +on: + schedule: + - cron: "23 * * * *" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: lineageweave-hourly-commercialization-loop + cancel-in-progress: false + +jobs: + inspect-pr-queue: + permissions: + actions: write + checks: read + contents: write + id-token: write + pull-requests: write + uses: ContextualWisdomLab/.github/.github/workflows/pr-review-merge-scheduler.yml@6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba + with: + base_branch: main + max_prs: "50" + trigger_reviews: true + review_dispatch_limit: "-1" + branch_update_limit: "-1" + enable_auto_merge: true + merge_mode: direct_or_auto + update_branches: true + secrets: inherit + + repair-review-feedback: + needs: inspect-pr-queue + if: ${{ always() }} + permissions: + actions: write + contents: read + issues: write + pull-requests: read + statuses: read + uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba + with: + target_repository: ContextualWisdomLab/LineageWeave + base_branch: main + max_prs: "50" + max_dispatches: "50" + retry_hours: "1" + canonical_ref: 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba + secrets: inherit + + revalidate-pr-queue: + needs: repair-review-feedback + if: ${{ always() }} + permissions: + actions: write + checks: read + contents: write + id-token: write + pull-requests: write + uses: ContextualWisdomLab/.github/.github/workflows/pr-review-merge-scheduler.yml@6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba + with: + base_branch: main + max_prs: "50" + trigger_reviews: true + review_dispatch_limit: "-1" + branch_update_limit: "-1" + enable_auto_merge: true + merge_mode: direct_or_auto + update_branches: true + secrets: inherit + + develop-next-product-gap: + needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue] + if: >- + ${{ + always() && + needs.inspect-pr-queue.result == 'success' && + needs.repair-review-feedback.result == 'success' && + needs.revalidate-pr-queue.result == 'success' + }} + runs-on: ubuntu-24.04 + timeout-minutes: 180 + permissions: + contents: read + id-token: write + pull-requests: read + services: + postgres: + image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777 + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + TARGET_REPOSITORY: ContextualWisdomLab/LineageWeave + BASE_BRANCH: main + PRODUCT_UX_SPEC: docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md + OPENCODE_VERSION: "1.17.13" + OPENCODE_SHA256: 157afa289d1a8d9372de0ce19ac726119b937a1f6b201808d46f06e4e59bb348 + OPENCODE_MODEL_CANDIDATES: >- + nvidia/nvidia/llama-3.3-nemotron-super-49b-v1.5 + nvidia/nvidia/nemotron-3-super-120b-a12b + nvidia/deepseek-ai/deepseek-v4-pro + OPENCODE_RED_TIMEOUT_SECONDS: "1200" + OPENCODE_IMPLEMENT_TIMEOUT_SECONDS: "5400" + MAX_AUTONOMOUS_CHANGED_FILES: "35" + MAX_AUTONOMOUS_FILE_BYTES: "524288" + MAX_AUTONOMOUS_TOTAL_BYTES: "1572864" + LINEAGEWEAVE_TEST_POSTGRES_ADMIN_DSN: postgresql://postgres:postgres@localhost:5432/postgres + UV_PROJECT_ENVIRONMENT: ${{ runner.temp }}/lineageweave-venv + + steps: + - name: Determine whether product development may start + id: gate + env: + GH_TOKEN: ${{ github.token }} + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + run: | + set -euo pipefail + if [ -z "${NVIDIA_API_KEY:-}" ]; then + echo "::warning::NVIDIA_NIM_API_KEY is not configured; product development remains fail-closed." + echo "eligible=false" >>"$GITHUB_OUTPUT" + exit 0 + fi + + open_pr_count="$( + gh api "/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1" \ + --jq 'length' + )" + if [ "$open_pr_count" -ne 0 ]; then + echo "An open pull request owns the queue; review, repair, checks, and merge stay ahead of new development." + echo "eligible=false" >>"$GITHUB_OUTPUT" + exit 0 + fi + + echo "eligible=true" >>"$GITHUB_OUTPUT" + + - name: Check out the protected default branch + if: steps.gate.outputs.eligible == 'true' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + ref: main + fetch-depth: 1 + persist-credentials: false + + - name: Set up Python + if: steps.gate.outputs.eligible == 'true' + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 + with: + python-version: "3.12" + + - name: Set up locked Python dependency manager + if: steps.gate.outputs.eligible == 'true' + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 + with: + version: "0.11.28" + enable-cache: false + + - name: Select the repository-pinned Rust toolchain + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + rustup toolchain install 1.97.1 --profile minimal + rustup default 1.97.1 + + - name: Set up Node + if: steps.gate.outputs.eligible == 'true' + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 + with: + node-version: "24" + + - name: Install the committed dependency locks + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + uv sync --frozen --extra dev --extra backend + chmod -R a+rX "$UV_PROJECT_ENVIRONMENT" + corepack enable + pnpm --dir frontend install --frozen-lockfile + echo "AUTOMATION_BASE_SHA=$(git rev-parse HEAD)" >>"$GITHUB_ENV" + { + echo "/opencode.json" + echo "/.agent-python-red-output.txt" + echo "/.agent-frontend-red-output.txt" + echo "/PR_MESSAGE.md" + } >>"$GITHUB_WORKSPACE/.git/info/exclude" + + - name: Verify the trusted base before authoring + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$AUTOMATION_BASE_SHA" + uv run --frozen python -m pytest -q + pnpm --dir frontend run lint + pnpm --dir frontend run test + pnpm --dir frontend run build + python -m compileall -q lineageweave backend tests + + - name: Install the pinned OpenCode CLI + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + archive="${RUNNER_TEMP}/opencode-linux-x64.tar.gz" + install_dir="${RUNNER_TEMP}/opencode/bin" + mkdir -p "$install_dir" + curl -fsSL \ + -o "$archive" \ + "https://github.com/anomalyco/opencode/releases/download/v${OPENCODE_VERSION}/opencode-linux-x64.tar.gz" + printf '%s %s\n' "$OPENCODE_SHA256" "$archive" | sha256sum -c - + tar -xzf "$archive" -C "$RUNNER_TEMP" + install -m 0755 "${RUNNER_TEMP}/opencode" "$install_dir/opencode" + "$install_dir/opencode" --version + echo "$install_dir" >>"$GITHUB_PATH" + + - name: Configure test-first authoring permissions + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + cat >"$GITHUB_WORKSPACE/opencode.json" <<'CONFIG' + { + "$schema": "https://opencode.ai/config.json", + "enabled_providers": ["nvidia"], + "model": "nvidia/nvidia/llama-3.3-nemotron-super-49b-v1.5", + "lsp": false, + "permission": { + "read": { + "*": "allow", + ".git/**": "deny", + "opencode.json": "deny", + ".env": "deny", + ".env.*": "deny" + }, + "edit": { + "*": "deny", + "tests/**": "allow", + "backend/tests/**": "allow", + "frontend/src/**/*.test.ts": "allow", + "frontend/src/**/*.test.tsx": "allow", + "docs/superpowers/specs/**": "allow" + }, + "bash": "deny", + "webfetch": "deny", + "websearch": "deny", + "external_directory": "deny", + "task": "deny", + "skill": "deny", + "question": "deny", + "lsp": "deny", + "doom_loop": "deny" + } + } + CONFIG + + - name: Author one bounded design supplement and failing regression + if: steps.gate.outputs.eligible == 'true' + env: + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + run: | + set -euo pipefail + prompt="$(cat <<'PROMPT' + Work only from the trusted files on the checked-out LineageWeave main + branch. Do not inspect GitHub issues, pull requests, external pages, + environment variables, credentials, or paths outside the repository. + + Read docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md + first. Select exactly one highest-impact buyer-visible gap needed to + implement that approved DB-grounded product design. Prefer the earliest + incomplete vertical slice in this order: Records and direct lineage; + record detail and cited evidence; analytical entity catalog; calendar + and calibrated reports; accounts and access; roles and read-only system + policy. The existing PostgreSQL cardinalities, API authorization rules, + synthetic-data boundary, and compact-navigation-versus-PROV-O boundary + are authoritative. Never invent account status, invitations, access + audit history, affiliation-scoped roles, causal lineage semantics, + lineage-change history, or editable ABAC behavior unless the same + bounded increment first adds the normalized persistence, API contract, + and tests that make the claim true. + + Write one concise design supplement under docs/superpowers/specs/ and + write the failing regression tests first. During this red phase edit + only tests/, backend/tests/, frontend test files, and + docs/superpowers/specs/. Do not modify production code, migrations, + package metadata, CHANGELOG, workflows, security policy, or ownership + files. Use realistic synthetic fixtures. A UI increment must test + keyboard-operable semantics and user-visible next-action copy. An API + or persistence increment must test RBAC then row-level affiliation + filtering and fail-closed validation. Do not write PR_MESSAGE.md yet. + PROMPT + )" + + status=1 + for model in $OPENCODE_MODEL_CANDIDATES; do + echo "::group::OpenCode red phase — $model" + if timeout --kill-after=30s "${OPENCODE_RED_TIMEOUT_SECONDS}s" \ + env -u GH_TOKEN -u GITHUB_TOKEN \ + -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ + HOME="${RUNNER_TEMP}/opencode-home-red" \ + opencode run "$prompt" --model "$model"; then + status=0 + echo "::endgroup::" + break + fi + echo "::endgroup::" + echo "::warning::Model $model failed in the red phase; restoring the trusted base." + git reset --hard "$AUTOMATION_BASE_SHA" + git clean -fd + done + if [ "$status" -ne 0 ]; then + echo "::error::Every NVIDIA model failed during test-first authoring." + exit 1 + fi + + - name: Enforce red-phase scope and observe a genuine failure + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + rm -f opencode.json + git clean -fdX -e frontend/node_modules/ + + python - <<'PY' + from __future__ import annotations + + import os + import stat + import subprocess + from pathlib import Path + + max_files = int(os.environ["MAX_AUTONOMOUS_CHANGED_FILES"]) + max_file_bytes = int(os.environ["MAX_AUTONOMOUS_FILE_BYTES"]) + max_total_bytes = int(os.environ["MAX_AUTONOMOUS_TOTAL_BYTES"]) + records = subprocess.check_output( + ["git", "status", "--porcelain=v1", "-z"] + ).split(b"\0") + paths: list[str] = [] + total_bytes = 0 + index = 0 + while index < len(records): + record = records[index] + index += 1 + if not record: + continue + status_text = record[:2].decode("ascii") + path_text = record[3:].decode("utf-8") + if any(marker in status_text for marker in ("R", "C", "D", "U")): + raise SystemExit( + f"red phase may not rename, copy, delete, or conflict: {path_text}" + ) + path = Path(path_text) + info = path.lstat() + if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode): + raise SystemExit(f"red phase produced a non-regular file: {path_text}") + data = path.read_bytes() + data.decode("utf-8", errors="strict") + if b"\0" in data: + raise SystemExit(f"red phase file contains a NUL byte: {path_text}") + if info.st_size > max_file_bytes: + raise SystemExit(f"red phase file exceeds the per-file budget: {path_text}") + paths.append(path_text) + total_bytes += info.st_size + + if not paths: + raise SystemExit("red phase produced no files") + if len(paths) > max_files or total_bytes > max_total_bytes: + raise SystemExit("red phase exceeded the autonomous diff budget") + def is_allowed_red_path(path_text: str) -> bool: + """Return whether one red-phase path is test or design evidence.""" + if path_text.startswith( + ("tests/", "backend/tests/", "docs/superpowers/specs/") + ): + return True + return path_text.startswith("frontend/src/") and path_text.endswith( + (".test.ts", ".test.tsx") + ) + + forbidden = [path for path in paths if not is_allowed_red_path(path)] + if forbidden: + raise SystemExit(f"red phase changed files outside its scope: {forbidden!r}") + if not any( + path.startswith(("tests/", "backend/tests/")) + or path.endswith((".test.ts", ".test.tsx")) + for path in paths + ): + raise SystemExit("red phase did not add or modify a regression test") + if not any(path.startswith("docs/superpowers/specs/") for path in paths): + raise SystemExit("red phase did not write a design supplement") + PY + + set +e + uv run --frozen python -m pytest -q \ + >"$GITHUB_WORKSPACE/.agent-python-red-output.txt" 2>&1 + python_status=$? + pnpm --dir frontend run test \ + >"$GITHUB_WORKSPACE/.agent-frontend-red-output.txt" 2>&1 + frontend_status=$? + set -e + + if [ "$python_status" -gt 1 ] || [ "$frontend_status" -gt 1 ]; then + cat .agent-python-red-output.txt + cat .agent-frontend-red-output.txt + echo "::error::A test runner failed for an infrastructure reason instead of a genuine assertion failure." + exit 1 + fi + if [ "$python_status" -eq 0 ] && [ "$frontend_status" -eq 0 ]; then + echo "::error::The red phase did not produce a failing regression." + exit 1 + fi + if [ "$python_status" -eq 1 ] && ! grep -Eq "FAILED|failed" .agent-python-red-output.txt; then + cat .agent-python-red-output.txt + echo "::error::Python red output did not contain a failed test." + exit 1 + fi + if [ "$frontend_status" -eq 1 ] && ! grep -Eq "FAIL|failed" .agent-frontend-red-output.txt; then + cat .agent-frontend-red-output.txt + echo "::error::Frontend red output did not contain a failed test." + exit 1 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add tests backend/tests frontend/src docs/superpowers/specs + git -c core.hooksPath=/dev/null commit \ + -m "test(red): define the next DB-grounded product gap" + echo "AUTOMATION_RED_SHA=$(git rev-parse HEAD)" >>"$GITHUB_ENV" + + - name: Configure implementation permissions + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + cat >"$GITHUB_WORKSPACE/opencode.json" <<'CONFIG' + { + "$schema": "https://opencode.ai/config.json", + "enabled_providers": ["nvidia"], + "model": "nvidia/nvidia/llama-3.3-nemotron-super-49b-v1.5", + "lsp": false, + "permission": { + "read": { + "*": "allow", + ".git/**": "deny", + "opencode.json": "deny", + ".env": "deny", + ".env.*": "deny", + ".agent-python-red-output.txt": "allow", + ".agent-frontend-red-output.txt": "allow" + }, + "edit": { + "*": "allow", + ".github/**": "deny", + ".git/**": "deny", + "AGENTS.md": "deny", + "CLAUDE.md": "deny", + "CODEOWNERS": "deny", + "SECURITY.md": "deny", + ".env": "deny", + ".env.*": "deny", + "opencode.json": "deny", + ".agent-python-red-output.txt": "deny", + ".agent-frontend-red-output.txt": "deny" + }, + "bash": "deny", + "webfetch": "deny", + "websearch": "deny", + "external_directory": "deny", + "task": "deny", + "skill": "deny", + "question": "deny", + "lsp": "deny", + "doom_loop": "deny" + } + } + CONFIG + + - name: Implement the bounded vertical slice + if: steps.gate.outputs.eligible == 'true' + env: + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + run: | + set -euo pipefail + prompt="$(cat <<'PROMPT' + Implement the single approved design supplement and failing regression + already present in the LineageWeave workspace. Read + .agent-python-red-output.txt and .agent-frontend-red-output.txt for the + exact red evidence. Do not inspect GitHub issues, pull requests, + external pages, environment variables, credentials, or paths outside + the repository. + + Make the smallest coherent production change that turns the red tests + green and advances the canonical DB-grounded product UX. Preserve: + source_post as evidence; direct post_lineage_edge versus indirect + Knowledge Graph links; account affiliations separate from global role + assignments; RBAC before row-level affiliation filtering; analytical + AUTO-* corporate entities separate from access assignment; synthetic + shipped data; standards-complete PROV-O separate from the compact + product graph; third-normal-form persistence; two-or-more-word + snake_case database objects; contextual-orchestrator's existing + pluggable-client and unavailable-channel behavior; and 100% docstrings + and owned-surface coverage for every new production module. + + For UI work, follow the canonical design spec and its Figma reference, + preserve keyboard and screen-reader semantics, and make every + explanation tell the user what action is available next. For DB/API + work, add upgrade and fresh-install paths together, fail closed, and + test real PostgreSQL behavior where applicable. Do not add unsupported + account status, invitation, access audit, affiliation-scoped role, + causality, lineage-change-history, or editable-ABAC claims. + + Update relevant architecture/operations documentation and create one + CHANGELOG.d/*.md fragment. Do not change workflows, security policy, + ownership files, or dependency sources. Write PR_MESSAGE.md with a concise PR title on the + first line and a body describing buyer impact, data/API compatibility, + test evidence, and exact validation commands. Do not commit, push, + approve, merge, publish, or release; the workflow owns those steps. + PROMPT + )" + + status=1 + for model in $OPENCODE_MODEL_CANDIDATES; do + echo "::group::OpenCode implementation phase — $model" + if timeout --kill-after=30s "${OPENCODE_IMPLEMENT_TIMEOUT_SECONDS}s" \ + env -u GH_TOKEN -u GITHUB_TOKEN \ + -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ + HOME="${RUNNER_TEMP}/opencode-home-implementation" \ + opencode run "$prompt" --model "$model"; then + status=0 + echo "::endgroup::" + break + fi + echo "::endgroup::" + echo "::warning::Model $model failed during implementation; restoring the verified red state." + git reset --hard "$AUTOMATION_RED_SHA" + git clean -fd + done + if [ "$status" -ne 0 ]; then + echo "::error::Every NVIDIA model failed during implementation." + exit 1 + fi + + - name: Enforce the autonomous implementation boundary + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + pr_message_backup="${RUNNER_TEMP}/agent-pr-message.md" + if [ -f PR_MESSAGE.md ]; then + cp PR_MESSAGE.md "$pr_message_backup" + fi + rm -f opencode.json .agent-python-red-output.txt .agent-frontend-red-output.txt + git clean -fdX -e frontend/node_modules/ + if [ -f "$pr_message_backup" ]; then + cp "$pr_message_backup" PR_MESSAGE.md + fi + + python - <<'PY' + from __future__ import annotations + + import os + import stat + import subprocess + from pathlib import Path + + base_sha = os.environ["AUTOMATION_BASE_SHA"] + max_files = int(os.environ["MAX_AUTONOMOUS_CHANGED_FILES"]) + max_file_bytes = int(os.environ["MAX_AUTONOMOUS_FILE_BYTES"]) + max_total_bytes = int(os.environ["MAX_AUTONOMOUS_TOTAL_BYTES"]) + + raw = subprocess.check_output( + ["git", "diff", "--name-status", "-z", base_sha] + ).split(b"\0") + changed: dict[str, str] = {} + index = 0 + while index < len(raw): + status_bytes = raw[index] + index += 1 + if not status_bytes: + continue + status_text = status_bytes.decode("ascii") + if status_text.startswith(("R", "C")): + old_path = raw[index].decode("utf-8") + new_path = raw[index + 1].decode("utf-8") + raise SystemExit( + f"autonomous changes may not rename or copy files: {old_path} -> {new_path}" + ) + path_text = raw[index].decode("utf-8") + index += 1 + status_code = status_text[0] + if status_code not in {"A", "M"}: + raise SystemExit( + f"autonomous status {status_text} is forbidden: {path_text}" + ) + changed[path_text] = status_code + + for value in subprocess.check_output( + ["git", "ls-files", "--others", "--exclude-standard", "-z"] + ).split(b"\0"): + if value: + changed[value.decode("utf-8")] = "?" + + if not changed: + raise SystemExit("implementation produced no changes") + if len(changed) > max_files: + raise SystemExit( + f"autonomous change count {len(changed)} exceeds {max_files}" + ) + + forbidden_exact = { + ".gitmodules", + "AGENTS.md", + "CLAUDE.md", + "CODEOWNERS", + "SECURITY.md", + } + forbidden_prefixes = (".github/", ".git/", "docker/keycloak/") + allowed_exact = { + "README.md", + "ARCHITECTURE.md", + "pyproject.toml", + "uv.lock", + "frontend/package.json", + "frontend/pnpm-lock.yaml", + "PR_MESSAGE.md", + } + allowed_prefixes = ( + "lineageweave/", + "backend/app/", + "backend/tests/", + "frontend/src/", + "migrations/", + "tests/", + "docs/", + "CHANGELOG.d/", + ) + allowed_suffixes = { + ".css", + ".html", + ".json", + ".md", + ".py", + ".sql", + ".toml", + ".ts", + ".tsx", + ".txt", + ".yaml", + ".yml", + } + + total_bytes = 0 + production_changed = False + test_changed = False + for path_text in sorted(changed): + path = Path(path_text) + if path.is_absolute() or ".." in path.parts: + raise SystemExit(f"invalid changed path: {path_text}") + if ( + path_text in forbidden_exact + or path_text.startswith(forbidden_prefixes) + or path.name.startswith(".env") + ): + raise SystemExit(f"protected path changed: {path_text}") + if ( + path_text not in allowed_exact + and not path_text.startswith(allowed_prefixes) + ): + raise SystemExit(f"path is outside autonomous scope: {path_text}") + if path_text != "PR_MESSAGE.md" and path.suffix not in allowed_suffixes: + raise SystemExit(f"unsupported path changed: {path_text}") + if path_text.startswith( + ("lineageweave/", "backend/app/", "frontend/src/", "migrations/") + ): + production_changed = True + if path_text.startswith(("tests/", "backend/tests/")) or path_text.endswith( + (".test.ts", ".test.tsx") + ): + test_changed = True + + info = path.lstat() + if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode): + raise SystemExit(f"non-regular file changed: {path_text}") + if info.st_size > max_file_bytes: + raise SystemExit(f"{path_text} exceeds the per-file byte budget") + data = path.read_bytes() + if b"\0" in data: + raise SystemExit(f"NUL byte found in {path_text}") + data.decode("utf-8", errors="strict") + total_bytes += info.st_size + + if not production_changed: + raise SystemExit("buyer-visible increment must change production code or schema") + if not test_changed: + raise SystemExit("autonomous increment must include regression tests") + if not any(path.startswith("docs/superpowers/specs/") for path in changed): + raise SystemExit("autonomous increment must include a design supplement") + if not any(path.startswith("CHANGELOG.d/") for path in changed): + raise SystemExit("autonomous increment must include a changelog fragment") + if "PR_MESSAGE.md" not in changed: + raise SystemExit("implementation must write PR_MESSAGE.md") + if total_bytes > max_total_bytes: + raise SystemExit( + f"autonomous byte total {total_bytes} exceeds {max_total_bytes}" + ) + PY + + - name: Validate the proposal in an isolated copy without network + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + validation_workspace="${RUNNER_TEMP}/lineageweave-validation-${GITHUB_RUN_ID}" + validation_home="${RUNNER_TEMP}/lineageweave-validation-home-${GITHUB_RUN_ID}" + validation_script="${RUNNER_TEMP}/lineageweave-validation-${GITHUB_RUN_ID}.sh" + sudo rm -rf "$validation_workspace" "$validation_home" "$validation_script" + mkdir -p "$validation_workspace" "$validation_home" + cp -a "$GITHUB_WORKSPACE/." "$validation_workspace/" + rm -rf "$validation_workspace/.git" + cat >"$validation_script" <<'VALIDATE' + set -euo pipefail + cd "$WORKSPACE" + uv run --frozen python -m pytest -q + uv run --frozen python -m compileall -q lineageweave backend tests + pnpm --dir frontend run lint + pnpm --dir frontend run test + pnpm --dir frontend run build + VALIDATE + chmod 0555 "$validation_script" + + sandbox_uid="$(id -u nobody)" + sandbox_gid="$(id -g nobody)" + sudo chown -R "$sandbox_uid:$sandbox_gid" "$validation_workspace" "$validation_home" + validation_sandbox=( + sudo unshare --net --pid --fork --mount-proc + setpriv + --reuid="$sandbox_uid" + --regid="$sandbox_gid" + --clear-groups + --no-new-privs + --bounding-set=-all + --inh-caps=-all + --ambient-caps=-all + ) + tool_path="$(dirname "$(command -v uv)")":"$(dirname "$(command -v pnpm)")":/usr/local/bin:/usr/bin:/bin + validation_environment=( + env -i + "PATH=$tool_path" + "HOME=$validation_home" + "WORKSPACE=$validation_workspace" + "UV_NO_SYNC=1" + "UV_PROJECT_ENVIRONMENT=$UV_PROJECT_ENVIRONMENT" + "PYTHONDONTWRITEBYTECODE=1" + "CI=true" + bash + --noprofile + --norc + "$validation_script" + ) + "${validation_sandbox[@]}" "${validation_environment[@]}" + + - name: Recheck queue and base before acquiring write authority + id: mutation_preflight + if: steps.gate.outputs.eligible == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + open_pr_count="$( + gh api "/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1" \ + --jq 'length' + )" + if [ "$open_pr_count" -ne 0 ]; then + echo "Another pull request acquired the queue before token exchange." + echo "eligible=false" >>"$GITHUB_OUTPUT" + exit 0 + fi + + current_base_sha="$( + gh api "/repos/${TARGET_REPOSITORY}/commits/${BASE_BRANCH}" --jq '.sha' + )" + if [ "$current_base_sha" != "$AUTOMATION_BASE_SHA" ]; then + echo "The base branch moved during authoring; the stale proposal will be discarded." + echo "eligible=false" >>"$GITHUB_OUTPUT" + exit 0 + fi + + echo "eligible=true" >>"$GITHUB_OUTPUT" + + - name: Exchange an OpenCode app token for the generated PR + id: generated_pr_token + if: steps.mutation_preflight.outputs.eligible == 'true' + env: + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai + run: | + set -euo pipefail + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || \ + [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + echo "::error::OIDC request environment is unavailable." + exit 1 + fi + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator="&" + case "$request_url" in + *\?*) ;; + *) separator="?" ;; + esac + oidc_response="$( + curl -fsS \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )" + oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" + if [ -z "$oidc_token" ]; then + echo "::error::OIDC token response was empty." + exit 1 + fi + token_response="$( + curl -fsS \ + -X POST \ + -H "Authorization: Bearer ${oidc_token}" \ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )" + app_token="$(jq -r '.token // empty' <<<"$token_response")" + if [ -z "$app_token" ]; then + echo "::error::OpenCode GitHub App token response was empty." + exit 1 + fi + echo "::add-mask::$app_token" + echo "token=$app_token" >>"$GITHUB_OUTPUT" + + - name: Open exactly one protected pull request + if: steps.mutation_preflight.outputs.eligible == 'true' + env: + GH_TOKEN: ${{ steps.generated_pr_token.outputs.token }} + run: | + set -euo pipefail + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::Generated PR app token is unavailable." + exit 1 + fi + + open_pr_count="$( + gh api "/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1" \ + --jq 'length' + )" + if [ "$open_pr_count" -ne 0 ]; then + echo "Another pull request acquired the queue; discarding this proposal." + exit 0 + fi + + current_base_sha="$( + gh api "/repos/${TARGET_REPOSITORY}/commits/${BASE_BRANCH}" --jq '.sha' + )" + if [ "$current_base_sha" != "$AUTOMATION_BASE_SHA" ]; then + echo "The base branch moved during authoring; discarding this stale proposal." + exit 0 + fi + + title="LineageWeave DB-grounded product increment" + body_file="${RUNNER_TEMP}/pr-body.md" + if [ -f PR_MESSAGE.md ]; then + python - <<'PY' + from __future__ import annotations + + import os + from pathlib import Path + + source = Path("PR_MESSAGE.md").read_text(encoding="utf-8") + lines = source.splitlines() + candidate = lines[0].lstrip("#").strip() if lines else "" + if ( + 10 <= len(candidate) <= 120 + and not candidate.startswith("-") + and all(character.isprintable() for character in candidate) + ): + title = candidate + else: + title = "LineageWeave DB-grounded product increment" + body = "\n".join(lines[1:]).strip() + if not body: + body = "Autonomous NVIDIA NIM increment; see the design supplement and changelog fragment." + if len(body.encode("utf-8")) > 20_000: + raise SystemExit("PR body exceeds 20,000 UTF-8 bytes") + Path(os.environ["RUNNER_TEMP"], "pr-title.txt").write_text( + title, + encoding="utf-8", + ) + Path(os.environ["RUNNER_TEMP"], "pr-body.md").write_text( + body + "\n", + encoding="utf-8", + ) + PY + title="$(cat "${RUNNER_TEMP}/pr-title.txt")" + rm -f PR_MESSAGE.md + else + echo "Autonomous NVIDIA NIM increment; see the design supplement and changelog fragment." \ + >"$body_file" + fi + + git reset --soft "$AUTOMATION_BASE_SHA" + branch="nim-agent/db-grounded-product-${GITHUB_RUN_ID}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add -A + git -c core.hooksPath=/dev/null commit -m "$title" + git -c core.hooksPath=/dev/null push \ + "https://x-access-token:${GH_TOKEN}@github.com/${TARGET_REPOSITORY}.git" \ + "HEAD:refs/heads/${branch}" + gh pr create \ + --repo "$TARGET_REPOSITORY" \ + --base "$BASE_BRANCH" \ + --head "$branch" \ + --title "$title" \ + --body-file "$body_file" diff --git a/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md b/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md new file mode 100644 index 000000000..a2d61a1de --- /dev/null +++ b/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md @@ -0,0 +1,20 @@ +# Hourly DB-grounded commercialization loop + +## Added + +- An hourly, review-first commercialization workflow now drains open pull + requests through current-head review, feedback repair, check revalidation, + branch refresh, and protected merge before creating more work. +- When the pull-request queue is empty, a pinned OpenCode CLI using only + `NVIDIA_NIM_API_KEY` selects one buyer-visible gap from the approved + DB-grounded Figma design, writes a failing regression first, implements one + bounded vertical slice, validates it in an unprivileged network-isolated + copy, and opens exactly one protected pull request. +- Permanent workflow-contract tests bind the schedule, immutable central + governance references, credential removal, no-Copilot rule, test-first + evidence, protected paths, stale-work checks, and no-self-merge boundary. +- Product design and doctoring documentation now records the truthful mapping + from PostgreSQL cardinalities to Records, Lineage, Record Detail, Entity + Catalog, Calendar, Reports, Accounts, Roles, and read-only system-policy + interactions, with APA 7th references for WCAG 2.2, WAI-ARIA 1.2, + ISO/IEC 25010:2023, ISO/IEC 40500:2025, and NIST zero-trust controls. diff --git a/docs/doctoring/PRODUCT_UX_REFERENCES.md b/docs/doctoring/PRODUCT_UX_REFERENCES.md new file mode 100644 index 000000000..7361150c0 --- /dev/null +++ b/docs/doctoring/PRODUCT_UX_REFERENCES.md @@ -0,0 +1,93 @@ +# Product UX and access-control references + +Reviewed: **2026-08-14** + +This bibliography records the primary standards used by the DB-grounded +product UX contract. It does not claim certification. Implementation evidence +must be produced by tests, accessibility review, security controls, operating +records, and the applicable assessor. + +## Normative and authoritative sources — APA 7th + +Campbell, A., Adams, C., Bradley Montgomery, R., Cooper, M., & Kirkpatrick, +A. (Eds.). (2024). *Web Content Accessibility Guidelines (WCAG) 2.2* +(W3C Recommendation, 12 December 2024). World Wide Web Consortium. +https://www.w3.org/TR/2024/REC-WCAG22-20241212/ + +Chandramouli, R., & Butcher, Z. (2023). *A zero trust architecture model for +access control in cloud-native applications in multi-cloud environments* +(NIST Special Publication 800-207A). National Institute of Standards and +Technology. https://doi.org/10.6028/NIST.SP.800-207A + +Diggs, J., Nurthen, J., Cooper, M., & MacLeod, C. (Eds.). (2023). +*Accessible Rich Internet Applications (WAI-ARIA) 1.2* +(W3C Recommendation, 6 June 2023). World Wide Web Consortium. +https://www.w3.org/TR/2023/REC-wai-aria-1.2-20230606/ + +International Organization for Standardization, & International +Electrotechnical Commission. (2023). *Systems and software engineering — +Systems and software Quality Requirements and Evaluation (SQuaRE) — Product +quality model* (ISO/IEC 25010:2023, 2nd ed.). +https://www.iso.org/standard/78176.html + +International Organization for Standardization, & International +Electrotechnical Commission. (2025). *Information technology — W3C Web +Content Accessibility Guidelines (WCAG) 2.2* (ISO/IEC 40500:2025, 2nd ed.). +https://www.iso.org/standard/91029.html + +Joint Task Force. (2020). *Security and privacy controls for information +systems and organizations* (NIST Special Publication 800-53, Revision 5, +including Update 1). National Institute of Standards and Technology. +https://doi.org/10.6028/NIST.SP.800-53r5 + +Rose, S., Borchert, O., Mitchell, S., & Connelly, S. (2020). *Zero trust +architecture* (NIST Special Publication 800-207). National Institute of +Standards and Technology. https://doi.org/10.6028/NIST.SP.800-207 + +## Currency notes + +- The current W3C Recommendation for WCAG 2.2 is dated 12 December 2024. +- ISO/IEC 40500:2025 was published in September 2025 as the international + standard edition of WCAG 2.2. ISO lists an edition 3 draft under development, + so the implementation should continue to track W3C and ISO publication + status rather than freezing an accessibility program around a label. +- ISO/IEC 25010:2023 is the published second edition of the product quality + model and replaces the 2011 edition. +- NIST issued SP 800-53 Release 5.2.0 on 27 August 2025. Control mappings must + name the exact catalog release used by an assessment. + +## Implementation traceability + +| Source | LineageWeave design or control | +|---|---| +| WCAG 2.2 | Keyboard operation, focus order and visibility, target size, contrast, accessible authentication, error identification, status messages | +| WAI-ARIA 1.2 | Accessible names, roles, states, dialog/evidence-drawer semantics, non-visual graph navigation | +| ISO/IEC 25010:2023 | Functional suitability, interaction capability, reliability, security, maintainability, flexibility, and acceptance criteria | +| NIST SP 800-207 | Authentication is not sufficient authorization; access is resource- and identity-focused | +| NIST SP 800-207A | Application-level identity and granular policy enforcement for modular services | +| NIST SP 800-53 Rev. 5 | Access control, identification and authentication, audit, configuration, system integrity, supply-chain, and privacy control families | + +## Product-specific decisions grounded by the sources + +1. The account screen separates identity, affiliation, role assignment, and + derived permissions because each has a distinct persistence and control + meaning. +2. The row-level rule is evaluated after coarse permission membership; a valid + token alone never grants record access. +3. Graph interactions require an equivalent semantic list or tree so SVG + position is not the only way to understand or operate the lineage. +4. Account lifecycle and access-audit interfaces remain absent until the + persistence and immutable audit evidence exist. +5. Explanatory copy names the available next action and does not assert causal + or historical facts absent from stored evidence. +6. Product quality acceptance is tied to testable contracts, not visual + similarity alone. + +## Certification boundary + +CSAP, SOC 2, and other assurance programs require organizational evidence +beyond repository code. This repository can provide technical controls and +traceability, but it must not claim certification from implementation alone. +A deployment readiness package should map the exact deployed configuration, +operating procedures, evidence retention, access review, incident handling, +supplier controls, and assessor scope. diff --git a/docs/operations/hourly-commercialization-loop.md b/docs/operations/hourly-commercialization-loop.md new file mode 100644 index 000000000..917f76c0a --- /dev/null +++ b/docs/operations/hourly-commercialization-loop.md @@ -0,0 +1,226 @@ +# Hourly LineageWeave commercialization loop + +## Purpose + +The hourly workflow turns the approved DB-grounded Figma design into protected, +reviewed product increments while keeping pull-request completion ahead of new +feature creation. + +The workflow file is +`.github/workflows/hourly-commercialization-loop.yml`. It runs at minute 23 of +every hour and can also be invoked manually. + +## Queue policy + +One pull request owns the development queue. + +```mermaid +flowchart TD + A[Hourly trigger] --> B[Inspect every open PR] + B --> C[Dispatch current-head review where missing] + C --> D[Repair actionable review feedback] + D --> E[Revalidate checks and branch freshness] + E --> F{Open PR remains?} + F -->|yes| A + F -->|no| G[Select one buyer-visible DB-grounded gap] + G --> H[Write design supplement and failing test] + H --> I[Implement one vertical slice] + I --> J[Validate in isolated copy without network] + J --> K{Queue and main unchanged?} + K -->|no| L[Discard stale proposal] + K -->|yes| M[Open exactly one PR] + M --> A +``` + +The central ContextualWisdomLab workflows own review dispatch, review-feedback +repair, branch updates, required-check evaluation, auto-merge, and direct merge. +The product-development job cannot approve or merge its own work. + +## Accuracy-first cadence + +The schedule is hourly, but `cancel-in-progress` is false. A long OpenCode run +queues later invocations rather than being killed at the next heartbeat. This +is intentional: current-head correctness and reproducible evidence take +precedence over wall-clock throughput. + +The product job has a 180-minute budget. It starts only after all three queue +jobs succeed and no open pull request remains. + +## Product selection + +The canonical source is +`docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md`. + +The agent selects one coherent buyer-visible vertical slice in this order: + +1. Records and direct Lineage; +2. Record Detail and cited evidence; +3. analytical Entity Catalog; +4. Calendar and calibrated Reports; +5. Accounts and Access; +6. Roles and read-only System Policy. + +A later item may be selected first only when earlier items are already +implemented or when the same bounded change is a prerequisite that makes an +earlier screen truthful. + +## Test-first authoring + +The red phase can edit only: + +- `tests/`; +- `backend/tests/`; +- frontend test files; +- `docs/superpowers/specs/`. + +It must produce both a design supplement and a genuine failing assertion. +Python and frontend runners are executed independently; infrastructure exits +are rejected rather than misclassified as a red test. + +The implementation phase reads the red evidence and may edit the bounded +product surface, tests, migrations, architecture and operations documentation, +version metadata when required, and a `CHANGELOG.d` fragment. + +## Product invariants + +Every generated increment must preserve: + +- source records as evidence; +- direct reconstruction separately from indirect Knowledge Graph navigation; +- account affiliations independently from account-global roles; +- effective permissions as role-derived values; +- RBAC before row-level affiliation filtering; +- synthetic identities and content in public fixtures; +- analytical `AUTO-*` organizations outside the access-assignment set; +- standards-complete PROV-O outside the compact navigation projection; +- third-normal-form persistence and two-or-more-word snake-case database + objects; +- contextual-orchestrator's explicit available/unavailable client contract; +- standalone deployment and modular ecosystem integration; +- public docstrings and complete owned-surface regression coverage. + +The agent must not invent account lifecycle state, invitations, access-audit +history, affiliation-scoped roles, causal lineage labels, lineage-change +history, or an editable ABAC language without first adding the normalized +storage and API contract. + +## Model and credential boundary + +Only OpenCode is used for autonomous authoring. The provider list is restricted +to NVIDIA and uses `NVIDIA_NIM_API_KEY`. + +The workflow does not reference `COPILOT_GITHUB_TOKEN`. + +OpenCode is installed from a versioned archive whose SHA-256 digest is +verified. Both model phases explicitly remove GitHub token and Actions OIDC +environment variables before execution. + +The agent receives no shell, web search, web fetch, external-directory, task, +skill, question, or LSP permission. It cannot execute tests itself; the +workflow performs deterministic validation after each phase. + +## Protected paths + +Autonomous code cannot modify: + +- `.github/`; +- `.git/`; +- `AGENTS.md`; +- `CLAUDE.md`; +- `CODEOWNERS`; +- `SECURITY.md`; +- Keycloak seed material; +- environment or credential files. + +Renames, copies, deletions, non-UTF-8 files, symbolic links, oversized files, +and changes outside the allowlist fail closed. + +Each increment must contain production code or schema, regression tests, a +design supplement, a changelog fragment, and a bounded PR message. + +## Validation boundary + +The trusted main branch is validated before the agent runs. + +The generated proposal is copied to a disposable directory with `.git` +removed. Validation runs: + +```text +uv run --frozen python -m pytest -q +python -m compileall -q lineageweave backend tests +pnpm --dir frontend run lint +pnpm --dir frontend run test +pnpm --dir frontend run build +``` + +The copy is owned by the unprivileged `nobody` account and executed in a new +network and process namespace with: + +- an empty inherited environment; +- no new privileges; +- all capability sets removed; +- no Git metadata; +- no network. + +Integration behavior that requires services remains subject to the ordinary +exact-head pull-request checks after the protected PR is opened. The isolated +run is an additional pre-publication boundary, not a replacement for required +CI. + +## Single-writer and stale-work protection + +The workflow checks the open-PR count: + +1. before authoring; +2. before acquiring an app token; and +3. immediately before pushing. + +It also binds work to the exact starting `main` SHA and rechecks that SHA before +token exchange and push. A concurrent PR or moved base discards the proposal. + +Write authority is acquired only after validation through the existing +OpenCode OIDC exchange. The generated short-lived token is masked. The workflow +pushes one run-specific branch and opens exactly one PR. + +## Review and merge + +Generated PRs enter the same central loop as human-authored PRs: + +1. required checks execute on the exact head; +2. OpenCode and other configured independent review planes inspect the current + head; +3. actionable comments are repaired; +4. checks re-run; +5. an independent approval is required; +6. auto-merge or direct merge occurs without bypass. + +Review wait time is not a blocker. Subsequent hourly invocations continue +repairing and revalidating the queue but do not create another product PR. + +## Failure behavior + +The workflow fails closed when: + +- `NVIDIA_NIM_API_KEY` is absent; +- all NVIDIA model candidates fail; +- the red phase changes production files or produces no failing assertion; +- the implementation crosses its path or byte budget; +- validation fails; +- the PR queue becomes occupied; +- `main` moves; +- OIDC or app-token exchange fails. + +A failure leaves no pushed autonomous branch unless the final, rechecked +mutation step was reached. + +## Operating evidence + +The permanent contract tests in +`tests/test_hourly_commercialization_workflow.py` verify the schedule, +governance pins, NVIDIA-only model path, credential removal, red/green +discipline, protected paths, isolated validation, stale-work checks, and +one-PR/no-self-merge boundary. + +The central scheduler remains independently active. This repository workflow +adds a LineageWeave-specific hourly heartbeat and product-gap generator; it +does not duplicate the central scheduler's implementation. diff --git a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md new file mode 100644 index 000000000..139160c33 --- /dev/null +++ b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md @@ -0,0 +1,402 @@ +# LineageWeave DB-grounded product UX design + +**Status:** Accepted implementation contract +**Date:** 2026-08-14 +**Figma:** `https://www.figma.com/design/UpjgFQEu4u2Kr2hmyorAqe` +**Canonical Figma page:** `DB-grounded UX` +**Superseded concepts:** `Archive — superseded drafts` + +## Goal + +Turn LineageWeave's existing developer-oriented data surfaces into a +buyer-usable product without claiming states, relationships, histories, or +authorization semantics that the persistence and API layers cannot support. + +The approved product has two clearly separated surfaces: + +1. an evidence-linked workspace for records, reconstructed lineage, + analytical identities, commitments, and calibrated reports; and +2. an administrative control surface for provisioned accounts, affiliations, + account-global roles, effective permissions, and the currently implemented + read-only access rule. + +The design does not make the relational schema visible merely for debugging. +It uses the schema to determine which nouns, actions, cardinalities, and +explanations are truthful. + +## Primary invariant: DB cardinality is the interaction contract + +The interface must preserve the following independent relationships. + +```mermaid +erDiagram + user_account ||--o{ account_affiliation : has + corporate_entity ||--o{ account_affiliation : scopes + process_unit o|--o{ account_affiliation : narrows + + user_account ||--o{ account_role_assignment : receives + access_role ||--o{ account_role_assignment : assigned + access_role ||--o{ role_permission : grants + + source_post ||--o| post_summary_result : summarized + post_summary_result ||--o{ post_summary_event : orders + post_summary_result ||--o{ post_summary_role : derives + source_post ||--o{ issue_ticket : owns + source_post ||--o{ post_lineage_edge : parent + source_post ||--o{ post_lineage_edge : child + + cataloged_person ||--o{ person_affiliation : has + source_post ||--o{ post_person_mention : mentions + cataloged_person ||--o{ post_person_mention : identified + source_post ||--o{ post_team_mention : mentions + cataloged_team ||--o{ post_team_mention : identified + source_post ||--o{ post_organization_mention : mentions + corporate_entity ||--o{ post_organization_mention : identified + + provenance_resource ||--o{ provenance_assertion : subject + provenance_relation_definition ||--o{ provenance_assertion : predicate +``` + +Consequences: + +- An account's affiliations and roles are edited in separate sections. +- A role assignment applies to the whole account because + `account_role_assignment` has no affiliation key. +- Effective permissions are derived through `role_permission`; they are not + editable account attributes. +- A `process_unit` is optional within an affiliation. +- A `source_post` remains the immutable evidence anchor for every derived + summary, actor, ticket, chat answer, report member, and lineage view. +- Direct lineage and Knowledge Graph navigation are distinct relation types. +- Standards-complete PROV-O data remains separate from the compact product + navigation graph. + +## Data and identity boundary + +The public product stack ships functional infrastructure with synthetic +identities and synthetic content, consistent with ADR 0001. The Figma file and +all committed screenshots use only synthetic names and values. + +The analytical entity catalog is not an access-assignment list: + +- `corporate_entity` contains the internal hierarchy used by affiliations; +- it can also contain externally discovered and corroborated customers, + partners, competitors, suppliers, and plants; +- automatically generated analytical rows use the `AUTO-` code namespace; +- the current schema has no normalized `identity_assignable_flag` or + `entity_origin_code`. + +Until that schema gap is closed, an affiliation picker must use a fail-closed +assignability rule that excludes analytical `AUTO-*` entities. The UI must +explain this boundary instead of silently exposing every corporate entity as an +access scope. + +## Product information architecture + +```text +Workspace +├── Records +├── Lineage +├── Calendar +├── Reports +└── Entities + +Administration +├── Accounts +├── Roles & permissions +└── System policy +``` + +There is no generic dashboard whose metrics depend on unmodeled lineage +history, assignment workflow, or account lifecycle state. + +## Screen 1 — Records and direct lineage + +### Buyer task + +Find a visible source record, understand which records form the most plausible +thread, and open the evidence behind a node. + +### Persisted sources + +- `source_post` +- `post_lineage_edge` +- `common_lookup_value` +- current account permissions and affiliations + +### Required behavior + +- List only rows that pass `post_read` and the row-level visibility rule. +- Search and filter by fields actually exposed by the API. +- Show VOC type and visibility labels resolved from lookup codes. +- Group direct-lineage nodes by the persisted reconstruction grouping. +- Label the graph as a **plausible parent-child reconstruction**. +- Show `fused_score` as a score, not as causal certainty. +- Keep indirect Knowledge Graph links visually and semantically separate. +- Show the rebuild action only to an account with `post_admin`. + +### Prohibited claims + +- cause, outcome, decision, or business-stage semantics not stored on an edge; +- "recently changed" without graph version and change-history persistence; +- automatic risk or priority labels without a persisted derivation contract. + +## Screen 2 — Record detail and cited evidence + +### Buyer task + +Read the source first, then inspect derived meaning and navigate to every +supporting record. + +### Persisted sources + +- `source_post` +- `post_summary_result` +- `post_summary_event` +- `post_summary_role` +- person, team, and organization mention tables +- `post_counterparty_entity` +- `issue_ticket` +- direct lineage plus indirect Knowledge Graph links +- `post_chat_result` and `post_chat_citation` +- the post activity stream + +### Required hierarchy + +1. source record and metadata; +2. summary and ordered key events; +3. roles and responsibilities with Person, Team, and Organization badges; +4. direct and indirect lineage neighborhood; +5. people, teams, organizations, affiliations, and corroboration status; +6. tickets and commitments; +7. activity; +8. lineage question and cited-source evidence. + +The source body is not replaced by a summary. A chat answer must expose its +cited `source_post` records and provide a next action to open evidence. + +### Unsupported surface + +A PROV-O explorer becomes product-visible only after the exact current runtime +persists product assertions and exposes an ABAC-protected API. Schema +availability alone is not sufficient product wiring. + +## Screen 3 — Analytical entity catalog + +### Buyer task + +See which stable people, teams, and organizations have been found across +records, understand their hierarchy and affiliations, and open every mentioned +record. + +### Persisted sources + +- `cataloged_person` +- `cataloged_team` +- `corporate_entity` +- `person_affiliation` +- post mention tables +- `organization_name_resolution` +- `post_counterparty_entity` +- `knowledge_graph_edge` + +### Required behavior + +- Distinguish People, Teams, and Organizations. +- Display the `corporate_entity.parent_entity_id` hierarchy. +- Preserve unresolved free-text affiliation names. +- Show canonical-name resolution separately from relationship corroboration. +- Display `AUTO-*` codes as analytical provenance, never as an access grant. +- Navigate from an identity to only records the current account may see. + +## Screen 4 — Calendar and reports + +### Buyer task + +Act on dated commitments and inspect comparable period outputs without losing +the member records that support a score. + +### Persisted sources + +- `issue_ticket` +- `report_period_score` +- `report_member_score` +- `report_item_parameter` +- `report_item_information` + +### Required behavior + +- Calendar includes open dated tickets ordered by `due_date`. +- A commitment shows the owning record and current ticket status. +- Reports expose grouping kind, grouping key, period, model, convergence, + mean theta, uncertainty, link method, anchor period, and member records. +- Report rebuild is available only with `post_admin`. +- The interface must not treat latent scores as source evidence; member records + remain available for drill-down. + +## Screen 5 — Accounts and access + +### Buyer task + +Provision an OIDC subject, assign internal affiliations, assign account-global +roles, and understand the resulting permissions. + +### Persisted sources + +- `user_account` +- `account_affiliation` +- `corporate_entity` +- `process_unit` +- `account_role_assignment` +- `access_role` +- `role_permission` + +### Required behavior + +The account detail page has four separate sections: + +1. identity: display name, email, external subject, and creation time; +2. affiliations: corporate entity plus optional process unit; +3. assigned roles: account-global role memberships; +4. effective permissions: the union derived from assigned roles. + +The UI explicitly warns that an account cannot be Viewer in one affiliation +and Admin in another under the current schema. + +### Not modeled + +Do not show or edit: + +- invitation state; +- active, suspended, or locked status; +- access-audit history; +- role assignment scoped to an affiliation. + +Those capabilities require normalized lifecycle, audit, or scoped-assignment +persistence and corresponding API contracts before entering the product. + +## Screen 6 — Roles and read-only system policy + +### Buyer task + +Understand the coarse role-permission matrix and the exact row-level rule that +the running service enforces. + +### Current vocabulary + +```text +viewer → post_read +admin → post_read + post_admin +``` + +The seeded vocabulary may grow, but the interface must render rows actually +stored in `access_role` and `role_permission`, not a fabricated Analyst role. + +### Runtime authorization sequence + +```mermaid +flowchart LR + A[Valid OIDC subject] --> B[Provisioned user_account] + B --> C[Role grants post_read] + C --> D{source_post visibility} + D -->|public| E[Allow] + D -->|private + matching account_affiliation| E + D -->|otherwise| F[Deny] +``` + +`abac_policy.condition_expression` is reserved for a future DSL. The current +backend evaluates the rule above directly. Therefore the policy surface is +read-only until a versioned evaluator, validation, simulation, rollback, and +audit contract exists. + +## Copy contract + +Every explanatory sentence helps the customer decide or take the next action. + +Good: + +- "Open the source record to inspect the evidence." +- "Add an internal affiliation to permit matching private records." +- "Assign a role to change the effective permission set." +- "Rebuild lineage to recompute direct parent-child candidates." + +Avoid: + +- implementation trivia without an action; +- raw UUIDs where a stable business label is available; +- reassuring claims that are not tied to persisted evidence. + +## Accessibility and interaction + +The implementation target is WCAG 2.2 Level AA and WAI-ARIA 1.2 semantics. + +- Every action is reachable and operable by keyboard. +- Focus order follows the visual and evidence hierarchy. +- Dialogs and evidence drawers manage focus and have accessible names. +- Status is conveyed by text, not color alone. +- Text and interactive controls meet applicable contrast requirements. +- Tables use headers and accessible row actions. +- Graph nodes have a non-visual list or tree representation with the same + navigation targets. +- Error and empty states identify the next available action. +- Reduced-motion preferences are respected. + +## Security, privacy, and auditability + +The design follows zero-trust principles: authentication does not imply record +access, and every record-scoped endpoint re-evaluates authorization. + +PII is not made unusable through indiscriminate masking. Instead: + +- access is least-privilege and row-scoped; +- the source record remains the evidence authority; +- derived identities retain provenance and visibility filtering; +- public fixtures are synthetic; +- production deployment requires retention, purpose, access review, export, + correction, and deletion controls appropriate to its jurisdiction; +- administrative writes require immutable audit persistence before an "access + audit" interface is claimed. + +## Delivery order + +When no PR owns the queue, implement the earliest incomplete coherent vertical +slice: + +1. application shell and Records/direct Lineage; +2. Record Detail and cited evidence; +3. analytical Entity Catalog; +4. Calendar and Reports; +5. Accounts and Access; +6. Roles and read-only System Policy; +7. only then add lifecycle, audit, scoped-role, graph-version, or provenance + explorer capabilities with their persistence and API contracts. + +Each slice includes: + +- production code; +- realistic synthetic regression tests; +- keyboard and accessibility assertions for UI work; +- migration plus fresh-install parity for schema changes; +- RBAC/ABAC integration tests for protected endpoints; +- architecture and user-action documentation; +- a changelog fragment; +- exact-head GitHub checks and an independent current-head approval. + +## Ecosystem boundaries + +- `mhtml-etl-gateway` owns governed ingestion of source artifacts. +- `contextual-orchestrator` owns LLM routing and multi-agent orchestration. +- `RankWeave` owns deterministic rank fusion. +- `ThreadWeave` owns thread assembly. +- `fast-mlsirm` and TEPP own calibrated measurement layers. +- `naruon` may import LineageWeave as a module, but LineageWeave remains usable + as a standalone service. +- Central `.github` owns review, repair, merge, and security governance. + +LineageWeave does not reimplement these capabilities. Integrations use explicit +wire contracts and unavailable-channel behavior rather than silent fallbacks. + +## References + +The authoritative APA 7 bibliography and implementation traceability are in +[`docs/doctoring/PRODUCT_UX_REFERENCES.md`](../../doctoring/PRODUCT_UX_REFERENCES.md). diff --git a/tests/test_hourly_commercialization_workflow.py b/tests/test_hourly_commercialization_workflow.py new file mode 100644 index 000000000..f710f0bd5 --- /dev/null +++ b/tests/test_hourly_commercialization_workflow.py @@ -0,0 +1,217 @@ +"""Contract tests for the hourly DB-grounded commercialization workflow. + +The workflow is security-sensitive production automation. These tests keep its +review-first queue discipline, NVIDIA-only OpenCode execution, test-first +authoring, sandboxed validation, and one-PR mutation boundary visible in code +review instead of relying on prose. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +_ROOT = Path(__file__).resolve().parents[1] +_WORKFLOW_PATH = _ROOT / ".github" / "workflows" / "hourly-commercialization-loop.yml" +_SPEC_PATH = ( + _ROOT + / "docs" + / "superpowers" + / "specs" + / "2026-08-14-db-grounded-product-ux-design.md" +) +_OPERATIONS_PATH = _ROOT / "docs" / "operations" / "hourly-commercialization-loop.md" +_DOCTORING_PATH = _ROOT / "docs" / "doctoring" / "PRODUCT_UX_REFERENCES.md" +_WORKFLOW = _WORKFLOW_PATH.read_text(encoding="utf-8") + + +def _job_text(job_name: str) -> str: + """Return one top-level job block from the workflow source.""" + marker = f" {job_name}:\n" + start = _WORKFLOW.index(marker) + match = re.search(r"^ [a-z0-9-]+:\n", _WORKFLOW[start + len(marker) :], re.MULTILINE) + if match is None: + return _WORKFLOW[start:] + return _WORKFLOW[start : start + len(marker) + match.start()] + + +def test_schedule_runs_hourly_without_cancelling_long_accuracy_work() -> None: + """The heartbeat is hourly while long OpenCode runs queue instead of being killed.""" + assert '- cron: "23 * * * *"' in _WORKFLOW + assert "group: lineageweave-hourly-commercialization-loop" in _WORKFLOW + assert "cancel-in-progress: false" in _WORKFLOW + assert "timeout-minutes: 180" in _WORKFLOW + + +def test_review_fix_check_merge_loop_precedes_new_development() -> None: + """Every run inspects, repairs, and revalidates PRs before opening new work.""" + assert _WORKFLOW.index(" inspect-pr-queue:") < _WORKFLOW.index( + " repair-review-feedback:" + ) + assert _WORKFLOW.index(" repair-review-feedback:") < _WORKFLOW.index( + " revalidate-pr-queue:" + ) + assert _WORKFLOW.index(" revalidate-pr-queue:") < _WORKFLOW.index( + " develop-next-product-gap:" + ) + develop = _job_text("develop-next-product-gap") + assert "needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue]" in develop + assert "open_pr_count" in develop + assert "An open pull request owns the queue" in develop + + +def test_central_review_and_repair_workflows_are_immutable_pins() -> None: + """Reusable governance workflows must use exact commits, never moving refs.""" + expected = ( + "ContextualWisdomLab/.github/.github/workflows/" + "pr-review-merge-scheduler.yml@" + "6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba" + ) + repair = ( + "ContextualWisdomLab/.github/.github/workflows/" + "pr-review-fix-scheduler.yml@" + "6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba" + ) + assert _WORKFLOW.count(expected) == 2 + assert repair in _WORKFLOW + assert "@main" not in "\n".join( + line for line in _WORKFLOW.splitlines() if "uses: ContextualWisdomLab/.github" in line + ) + + +def test_scheduler_is_configured_to_exhaust_the_pr_queue() -> None: + """Review and branch-update budgets do not strand additional eligible PRs.""" + assert _WORKFLOW.count('review_dispatch_limit: "-1"') == 2 + assert _WORKFLOW.count('branch_update_limit: "-1"') == 2 + assert _WORKFLOW.count("merge_mode: direct_or_auto") == 2 + assert _WORKFLOW.count("enable_auto_merge: true") == 2 + assert 'max_dispatches: "50"' in _WORKFLOW + + +def test_product_agent_uses_only_nvidia_nim_and_pinned_opencode() -> None: + """No Copilot token or unverified OpenCode binary may enter the product loop.""" + assert "NVIDIA_NIM_API_KEY" in _WORKFLOW + assert "NVIDIA_API_KEY" in _WORKFLOW + assert "COPILOT_GITHUB_TOKEN" not in _WORKFLOW + assert 'OPENCODE_VERSION: "1.17.13"' in _WORKFLOW + assert ( + "OPENCODE_SHA256: " + "157afa289d1a8d9372de0ce19ac726119b937a1f6b201808d46f06e4e59bb348" + in _WORKFLOW + ) + assert "sha256sum -c -" in _WORKFLOW + assert "enabled_providers" in _WORKFLOW + assert '["nvidia"]' in _WORKFLOW + + +def test_agent_never_receives_github_or_oidc_credentials() -> None: + """Both OpenCode phases explicitly remove write-authority environment values.""" + assert _WORKFLOW.count("env -u GH_TOKEN -u GITHUB_TOKEN") == 2 + assert _WORKFLOW.count( + "-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL" + ) == 2 + assert _WORKFLOW.index("Recheck queue and base before acquiring write authority") < ( + _WORKFLOW.index("Exchange an OpenCode app token for the generated PR") + ) + + +def test_agent_permissions_are_test_first_and_deny_shell_or_web() -> None: + """The red phase can author evidence only; neither phase can execute tools.""" + assert '"*": "deny"' in _WORKFLOW + assert '"tests/**": "allow"' in _WORKFLOW + assert '"backend/tests/**": "allow"' in _WORKFLOW + assert '"frontend/src/**/*.test.tsx": "allow"' in _WORKFLOW + assert _WORKFLOW.count('"bash": "deny"') == 2 + assert _WORKFLOW.count('"webfetch": "deny"') == 2 + assert _WORKFLOW.count('"websearch": "deny"') == 2 + assert _WORKFLOW.count('"question": "deny"') == 2 + + +def test_red_state_requires_real_failing_tests_and_a_design_supplement() -> None: + """A prose-only or already-green proposal cannot advance to implementation.""" + assert "red phase did not add or modify a regression test" in _WORKFLOW + assert "red phase did not write a design supplement" in _WORKFLOW + assert "The red phase did not produce a failing regression" in _WORKFLOW + assert ".agent-python-red-output.txt" in _WORKFLOW + assert ".agent-frontend-red-output.txt" in _WORKFLOW + assert "AUTOMATION_RED_SHA" in _WORKFLOW + + +def test_implementation_preserves_governance_and_db_grounded_boundaries() -> None: + """Protected policy files and unsupported product claims remain out of scope.""" + develop = _job_text("develop-next-product-gap") + for protected in ( + '".github/**": "deny"', + '"AGENTS.md": "deny"', + '"CLAUDE.md": "deny"', + '"CODEOWNERS": "deny"', + '"SECURITY.md": "deny"', + ): + assert protected in develop + assert "account affiliations separate from global role" in develop + assert "AUTO-*" in develop + assert "access assignment" in develop + assert "standards-complete PROV-O separate from the compact" in develop + assert "Do not add unsupported" in develop + + +def test_every_increment_requires_production_tests_spec_and_changelog() -> None: + """One bounded PR must contain the complete buyer-visible vertical slice.""" + assert "buyer-visible increment must change production code or schema" in _WORKFLOW + assert "autonomous increment must include regression tests" in _WORKFLOW + assert "autonomous increment must include a design supplement" in _WORKFLOW + assert "autonomous increment must include a changelog fragment" in _WORKFLOW + assert "implementation must write PR_MESSAGE.md" in _WORKFLOW + + +def test_untrusted_validation_is_networkless_unprivileged_and_complete() -> None: + """The proposal is tested in a disposable copy without inherited credentials.""" + assert "cp -a \"$GITHUB_WORKSPACE/.\" \"$validation_workspace/\"" in _WORKFLOW + assert 'rm -rf "$validation_workspace/.git"' in _WORKFLOW + assert "sudo unshare --net --pid --fork --mount-proc" in _WORKFLOW + assert "--no-new-privs" in _WORKFLOW + assert "--bounding-set=-all" in _WORKFLOW + assert "env -i" in _WORKFLOW + for command in ( + "uv run --frozen python -m pytest -q", + "python -m compileall -q lineageweave backend tests", + "pnpm --dir frontend run lint", + "pnpm --dir frontend run test", + "pnpm --dir frontend run build", + ): + assert command in _WORKFLOW + + +def test_mutation_rechecks_single_writer_and_base_freshness() -> None: + """A concurrent PR or moved main branch discards stale autonomous work.""" + assert _WORKFLOW.count( + 'gh api "/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1"' + ) >= 3 + assert _WORKFLOW.count( + 'gh api "/repos/${TARGET_REPOSITORY}/commits/${BASE_BRANCH}"' + ) >= 2 + assert "Another pull request acquired the queue; discarding this proposal." in _WORKFLOW + assert "The base branch moved during authoring; discarding this stale proposal." in _WORKFLOW + + +def test_agent_cannot_approve_merge_or_release() -> None: + """The product job opens one PR and delegates all review and merge authority.""" + develop = _job_text("develop-next-product-gap") + assert "gh pr create" in develop + assert "gh pr merge" not in develop + assert "gh pr review --approve" not in develop + assert "gh release create" not in develop + assert "Do not commit, push" in develop + assert "approve, merge, publish, or release" in develop + + +def test_canonical_product_and_operations_documents_exist() -> None: + """Automation must stay bound to the reviewed product and operations contracts.""" + assert _SPEC_PATH.is_file() + assert _OPERATIONS_PATH.is_file() + assert _DOCTORING_PATH.is_file() + assert str(_SPEC_PATH.relative_to(_ROOT)) in _WORKFLOW + spec = _SPEC_PATH.read_text(encoding="utf-8") + assert "DB cardinality is the interaction contract" in spec + assert "Archive — superseded drafts" in spec + assert "UpjgFQEu4u2Kr2hmyorAqe" in spec From de8d1d15e42efca4e108f23d6bfd4b7d71a5f1fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 22:41:27 +0900 Subject: [PATCH 02/17] fix(ci): address hourly loop review findings --- .../hourly-commercialization-loop.yml | 7 +++- .../hourly-commercialization-loop.md | 12 ++++--- ...026-08-14-db-grounded-product-ux-design.md | 2 +- .../test_hourly_commercialization_workflow.py | 32 ++++++++++++++++++- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/.github/workflows/hourly-commercialization-loop.yml b/.github/workflows/hourly-commercialization-loop.yml index 9fce2e9b8..d74bf280b 100644 --- a/.github/workflows/hourly-commercialization-loop.yml +++ b/.github/workflows/hourly-commercialization-loop.yml @@ -115,7 +115,6 @@ jobs: MAX_AUTONOMOUS_FILE_BYTES: "524288" MAX_AUTONOMOUS_TOTAL_BYTES: "1572864" LINEAGEWEAVE_TEST_POSTGRES_ADMIN_DSN: postgresql://postgres:postgres@localhost:5432/postgres - UV_PROJECT_ENVIRONMENT: ${{ runner.temp }}/lineageweave-venv steps: - name: Determine whether product development may start @@ -164,6 +163,12 @@ jobs: version: "0.11.28" enable-cache: false + - name: Pin the project virtual environment path + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + echo "UV_PROJECT_ENVIRONMENT=${RUNNER_TEMP}/lineageweave-venv" >>"$GITHUB_ENV" + - name: Select the repository-pinned Rust toolchain if: steps.gate.outputs.eligible == 'true' run: | diff --git a/docs/operations/hourly-commercialization-loop.md b/docs/operations/hourly-commercialization-loop.md index 917f76c0a..d9e636f94 100644 --- a/docs/operations/hourly-commercialization-loop.md +++ b/docs/operations/hourly-commercialization-loop.md @@ -147,7 +147,7 @@ removed. Validation runs: ```text uv run --frozen python -m pytest -q -python -m compileall -q lineageweave backend tests +uv run --frozen python -m compileall -q lineageweave backend tests pnpm --dir frontend run lint pnpm --dir frontend run test pnpm --dir frontend run build @@ -162,10 +162,12 @@ network and process namespace with: - no Git metadata; - no network. -Integration behavior that requires services remains subject to the ordinary -exact-head pull-request checks after the protected PR is opened. The isolated -run is an additional pre-publication boundary, not a replacement for required -CI. +PostgreSQL, Keycloak, and Valkey integration modules in this repository use +module-level availability probes and `pytest.mark.skipif`; inside the empty, +networkless environment they skip rather than error. Their authoritative real- +service execution remains the ordinary exact-head pull-request checks after +the protected PR is opened. The isolated run is an additional pre-publication +boundary, not a replacement for required CI. ## Single-writer and stale-work protection diff --git a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md index 139160c33..e55b4821c 100644 --- a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md +++ b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md @@ -32,7 +32,7 @@ The interface must preserve the following independent relationships. erDiagram user_account ||--o{ account_affiliation : has corporate_entity ||--o{ account_affiliation : scopes - process_unit o|--o{ account_affiliation : narrows + process_unit |o--o{ account_affiliation : narrows user_account ||--o{ account_role_assignment : receives access_role ||--o{ account_role_assignment : assigned diff --git a/tests/test_hourly_commercialization_workflow.py b/tests/test_hourly_commercialization_workflow.py index f710f0bd5..11d1e205e 100644 --- a/tests/test_hourly_commercialization_workflow.py +++ b/tests/test_hourly_commercialization_workflow.py @@ -137,6 +137,23 @@ def test_red_state_requires_real_failing_tests_and_a_design_supplement() -> None assert "AUTOMATION_RED_SHA" in _WORKFLOW +def test_virtual_environment_path_is_exported_from_step_scope() -> None: + """Runner-scoped paths are expanded in a step before later commands consume them.""" + develop = _job_text("develop-next-product-gap") + env_block = develop.split(" steps:\n", maxsplit=1)[0] + assert "runner.temp" not in env_block + setup_step = """ - name: Pin the project virtual environment path + if: steps.gate.outputs.eligible == 'true' + run: | + set -euo pipefail + echo "UV_PROJECT_ENVIRONMENT=${RUNNER_TEMP}/lineageweave-venv" >>"$GITHUB_ENV" +""" + assert setup_step in develop + assert develop.index("Set up locked Python dependency manager") < develop.index( + "Pin the project virtual environment path" + ) < develop.index("Install the committed dependency locks") + + def test_implementation_preserves_governance_and_db_grounded_boundaries() -> None: """Protected policy files and unsupported product claims remain out of scope.""" develop = _job_text("develop-next-product-gap") @@ -174,7 +191,7 @@ def test_untrusted_validation_is_networkless_unprivileged_and_complete() -> None assert "env -i" in _WORKFLOW for command in ( "uv run --frozen python -m pytest -q", - "python -m compileall -q lineageweave backend tests", + "uv run --frozen python -m compileall -q lineageweave backend tests", "pnpm --dir frontend run lint", "pnpm --dir frontend run test", "pnpm --dir frontend run build", @@ -182,6 +199,15 @@ def test_untrusted_validation_is_networkless_unprivileged_and_complete() -> None assert command in _WORKFLOW +def test_networkless_validation_keeps_real_service_evidence_in_exact_head_ci() -> None: + """Operations guidance must distinguish sandbox skips from real-service CI.""" + operations = _OPERATIONS_PATH.read_text(encoding="utf-8") + assert "module-level availability probes" in operations + assert "pytest.mark.skipif" in operations + assert "they skip rather than error" in operations + assert "ordinary exact-head pull-request checks" in operations + + def test_mutation_rechecks_single_writer_and_base_freshness() -> None: """A concurrent PR or moved main branch discards stale autonomous work.""" assert _WORKFLOW.count( @@ -212,6 +238,10 @@ def test_canonical_product_and_operations_documents_exist() -> None: assert _DOCTORING_PATH.is_file() assert str(_SPEC_PATH.relative_to(_ROOT)) in _WORKFLOW spec = _SPEC_PATH.read_text(encoding="utf-8") + operations = _OPERATIONS_PATH.read_text(encoding="utf-8") assert "DB cardinality is the interaction contract" in spec + assert "process_unit |o--o{ account_affiliation : narrows" in spec + assert "process_unit o|--o{ account_affiliation : narrows" not in spec assert "Archive — superseded drafts" in spec assert "UpjgFQEu4u2Kr2hmyorAqe" in spec + assert "uv run --frozen python -m compileall -q lineageweave backend tests" in operations From c5a0accb288f470344e95b91dde5ea4367fb501b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 23:18:21 +0900 Subject: [PATCH 03/17] fix(ci): require real frontend production changes --- .../hourly-commercialization-loop.yml | 28 ++++++++++--- .../test_hourly_commercialization_workflow.py | 42 +++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hourly-commercialization-loop.yml b/.github/workflows/hourly-commercialization-loop.yml index d74bf280b..c6264675b 100644 --- a/.github/workflows/hourly-commercialization-loop.yml +++ b/.github/workflows/hourly-commercialization-loop.yml @@ -655,6 +655,26 @@ jobs: ".yml", } + def is_frontend_test_path(path_text: str) -> bool: + """Return whether a frontend path is regression evidence, not product code.""" + return path_text.startswith("frontend/src/") and path_text.endswith( + (".test.ts", ".test.tsx") + ) + + def is_test_path(path_text: str) -> bool: + """Return whether a path contributes regression evidence.""" + return path_text.startswith(("tests/", "backend/tests/")) or ( + is_frontend_test_path(path_text) + ) + + def is_production_path(path_text: str) -> bool: + """Return whether a path changes product code or database schema.""" + if path_text.startswith(("lineageweave/", "backend/app/", "migrations/")): + return True + return path_text.startswith("frontend/src/") and not is_frontend_test_path( + path_text + ) + total_bytes = 0 production_changed = False test_changed = False @@ -675,13 +695,9 @@ jobs: raise SystemExit(f"path is outside autonomous scope: {path_text}") if path_text != "PR_MESSAGE.md" and path.suffix not in allowed_suffixes: raise SystemExit(f"unsupported path changed: {path_text}") - if path_text.startswith( - ("lineageweave/", "backend/app/", "frontend/src/", "migrations/") - ): + if is_production_path(path_text): production_changed = True - if path_text.startswith(("tests/", "backend/tests/")) or path_text.endswith( - (".test.ts", ".test.tsx") - ): + if is_test_path(path_text): test_changed = True info = path.lstat() diff --git a/tests/test_hourly_commercialization_workflow.py b/tests/test_hourly_commercialization_workflow.py index 11d1e205e..8cd2d2584 100644 --- a/tests/test_hourly_commercialization_workflow.py +++ b/tests/test_hourly_commercialization_workflow.py @@ -9,6 +9,7 @@ from __future__ import annotations import re +import textwrap from pathlib import Path _ROOT = Path(__file__).resolve().parents[1] @@ -35,6 +36,26 @@ def _job_text(job_name: str) -> str: return _WORKFLOW[start : start + len(marker) + match.start()] +def _implementation_path_classifiers() -> dict[str, object]: + """Load the pure path classifiers from the workflow's boundary script.""" + develop = _job_text("develop-next-product-gap") + boundary = develop.split( + " - name: Enforce the autonomous implementation boundary\n", maxsplit=1 + )[1].split( + " - name: Validate the proposal in an isolated copy without network\n", + maxsplit=1, + )[0] + match = re.search( + r"^ def is_frontend_test_path\(.*?(?=^ total_bytes = 0$)", + boundary, + flags=re.MULTILINE | re.DOTALL, + ) + assert match is not None, "workflow path classifiers were not found" + namespace: dict[str, object] = {} + exec(textwrap.dedent(match.group(0)), namespace) + return namespace + + def test_schedule_runs_hourly_without_cancelling_long_accuracy_work() -> None: """The heartbeat is hourly while long OpenCode runs queue instead of being killed.""" assert '- cron: "23 * * * *"' in _WORKFLOW @@ -181,6 +202,27 @@ def test_every_increment_requires_production_tests_spec_and_changelog() -> None: assert "implementation must write PR_MESSAGE.md" in _WORKFLOW +def test_frontend_test_only_diff_does_not_satisfy_production_gate() -> None: + """Regression files count as evidence but never as buyer-visible product code.""" + classifiers = _implementation_path_classifiers() + is_production_path = classifiers["is_production_path"] + is_test_path = classifiers["is_test_path"] + assert callable(is_production_path) + assert callable(is_test_path) + + assert is_test_path("frontend/src/App.test.tsx") is True + assert is_test_path("frontend/src/lineageLayout.test.ts") is True + assert is_production_path("frontend/src/App.test.tsx") is False + assert is_production_path("frontend/src/lineageLayout.test.ts") is False + + assert is_production_path("frontend/src/App.tsx") is True + assert is_production_path("frontend/src/App.css") is True + assert is_production_path("backend/app/main.py") is True + assert is_production_path("lineageweave/reconstruct.py") is True + assert is_production_path("migrations/0001_initial_schema.sql") is True + assert is_production_path("docs/architecture-note.md") is False + + def test_untrusted_validation_is_networkless_unprivileged_and_complete() -> None: """The proposal is tested in a disposable copy without inherited credentials.""" assert "cp -a \"$GITHUB_WORKSPACE/.\" \"$validation_workspace/\"" in _WORKFLOW From cb688ae88c3f68ca07a7988a2fc0338064a0dcb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 23:45:16 +0900 Subject: [PATCH 04/17] fix(ci): recognize every frontend test convention --- .../hourly-commercialization-loop.yml | 28 +++++++++--- .../test_hourly_commercialization_workflow.py | 43 +++++++++++++------ 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/.github/workflows/hourly-commercialization-loop.yml b/.github/workflows/hourly-commercialization-loop.yml index c6264675b..6be0a92b0 100644 --- a/.github/workflows/hourly-commercialization-loop.yml +++ b/.github/workflows/hourly-commercialization-loop.yml @@ -249,6 +249,9 @@ jobs: "backend/tests/**": "allow", "frontend/src/**/*.test.ts": "allow", "frontend/src/**/*.test.tsx": "allow", + "frontend/src/**/*.spec.ts": "allow", + "frontend/src/**/*.spec.tsx": "allow", + "frontend/src/**/__tests__/**": "allow", "docs/superpowers/specs/**": "allow" }, "bash": "deny", @@ -375,22 +378,31 @@ jobs: raise SystemExit("red phase produced no files") if len(paths) > max_files or total_bytes > max_total_bytes: raise SystemExit("red phase exceeded the autonomous diff budget") + + def is_frontend_test_path(path_text: str) -> bool: + """Return whether a frontend path is regression evidence.""" + path_parts = path_text.split("/") + return path_text.startswith("frontend/src/") and ( + path_text.endswith( + (".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx") + ) + or "__tests__" in path_parts + ) + def is_allowed_red_path(path_text: str) -> bool: """Return whether one red-phase path is test or design evidence.""" if path_text.startswith( ("tests/", "backend/tests/", "docs/superpowers/specs/") ): return True - return path_text.startswith("frontend/src/") and path_text.endswith( - (".test.ts", ".test.tsx") - ) + return is_frontend_test_path(path_text) forbidden = [path for path in paths if not is_allowed_red_path(path)] if forbidden: raise SystemExit(f"red phase changed files outside its scope: {forbidden!r}") if not any( path.startswith(("tests/", "backend/tests/")) - or path.endswith((".test.ts", ".test.tsx")) + or is_frontend_test_path(path) for path in paths ): raise SystemExit("red phase did not add or modify a regression test") @@ -657,8 +669,12 @@ jobs: def is_frontend_test_path(path_text: str) -> bool: """Return whether a frontend path is regression evidence, not product code.""" - return path_text.startswith("frontend/src/") and path_text.endswith( - (".test.ts", ".test.tsx") + path_parts = path_text.split("/") + return path_text.startswith("frontend/src/") and ( + path_text.endswith( + (".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx") + ) + or "__tests__" in path_parts ) def is_test_path(path_text: str) -> bool: diff --git a/tests/test_hourly_commercialization_workflow.py b/tests/test_hourly_commercialization_workflow.py index 8cd2d2584..e1cc8904e 100644 --- a/tests/test_hourly_commercialization_workflow.py +++ b/tests/test_hourly_commercialization_workflow.py @@ -137,11 +137,18 @@ def test_agent_never_receives_github_or_oidc_credentials() -> None: def test_agent_permissions_are_test_first_and_deny_shell_or_web() -> None: - """The red phase can author evidence only; neither phase can execute tools.""" + """The red phase can author all supported tests; neither phase can execute tools.""" assert '"*": "deny"' in _WORKFLOW assert '"tests/**": "allow"' in _WORKFLOW assert '"backend/tests/**": "allow"' in _WORKFLOW - assert '"frontend/src/**/*.test.tsx": "allow"' in _WORKFLOW + for pattern in ( + '"frontend/src/**/*.test.ts": "allow"', + '"frontend/src/**/*.test.tsx": "allow"', + '"frontend/src/**/*.spec.ts": "allow"', + '"frontend/src/**/*.spec.tsx": "allow"', + '"frontend/src/**/__tests__/**": "allow"', + ): + assert pattern in _WORKFLOW assert _WORKFLOW.count('"bash": "deny"') == 2 assert _WORKFLOW.count('"webfetch": "deny"') == 2 assert _WORKFLOW.count('"websearch": "deny"') == 2 @@ -203,23 +210,33 @@ def test_every_increment_requires_production_tests_spec_and_changelog() -> None: def test_frontend_test_only_diff_does_not_satisfy_production_gate() -> None: - """Regression files count as evidence but never as buyer-visible product code.""" + """Every supported frontend test convention is evidence, never product code.""" classifiers = _implementation_path_classifiers() is_production_path = classifiers["is_production_path"] is_test_path = classifiers["is_test_path"] assert callable(is_production_path) assert callable(is_test_path) - assert is_test_path("frontend/src/App.test.tsx") is True - assert is_test_path("frontend/src/lineageLayout.test.ts") is True - assert is_production_path("frontend/src/App.test.tsx") is False - assert is_production_path("frontend/src/lineageLayout.test.ts") is False - - assert is_production_path("frontend/src/App.tsx") is True - assert is_production_path("frontend/src/App.css") is True - assert is_production_path("backend/app/main.py") is True - assert is_production_path("lineageweave/reconstruct.py") is True - assert is_production_path("migrations/0001_initial_schema.sql") is True + frontend_test_paths = ( + "frontend/src/App.test.tsx", + "frontend/src/lineageLayout.test.ts", + "frontend/src/App.spec.tsx", + "frontend/src/lineageLayout.spec.ts", + "frontend/src/__tests__/App.tsx", + "frontend/src/components/__tests__/LineageCard.tsx", + ) + for path in frontend_test_paths: + assert is_test_path(path) is True + assert is_production_path(path) is False + + for path in ( + "frontend/src/App.tsx", + "frontend/src/App.css", + "backend/app/main.py", + "lineageweave/reconstruct.py", + "migrations/0001_initial_schema.sql", + ): + assert is_production_path(path) is True assert is_production_path("docs/architecture-note.md") is False From 5c28e373bdad3136471934290aa43de29283c978 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:42:38 +0900 Subject: [PATCH 05/17] test(red): bind accepted Figma pages to the product contract --- tests/test_figma_contract_sync.py | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_figma_contract_sync.py diff --git a/tests/test_figma_contract_sync.py b/tests/test_figma_contract_sync.py new file mode 100644 index 000000000..e25d10f9b --- /dev/null +++ b/tests/test_figma_contract_sync.py @@ -0,0 +1,33 @@ +"""Bind the accepted product contract to concrete Figma pages and claims.""" + +from __future__ import annotations + +from pathlib import Path + +_ROOT = Path(__file__).resolve().parents[1] +_SPEC = ( + _ROOT + / "docs" + / "superpowers" + / "specs" + / "2026-08-14-db-grounded-product-ux-design.md" +) + + +def test_figma_page_nodes_are_part_of_the_accepted_contract() -> None: + """The canonical and archived pages must be unambiguous in automation.""" + content = _SPEC.read_text(encoding="utf-8") + assert "**Canonical Figma page node:** `9:2`" in content + assert "**Archive Figma page node:** `0:1`" in content + assert "**Last contract synchronization:** `2026-08-15`" in content + + +def test_figma_contract_archives_unsupported_product_claims() -> None: + """Attractive unmodeled screens stay archived rather than becoming claims.""" + content = _SPEC.read_text(encoding="utf-8") + for statement in ( + "Graph change history, the PROV-O product explorer, and access audit remain archived", + "The canonical role vocabulary is `viewer` and `admin`; Figma does not fabricate an `Analyst` role.", + "Direct lineage remains a plausible parent-child reconstruction, never a causal claim.", + ): + assert statement in content From 2c04060550f16c2199fc3363743bdb1374f831f5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:45:28 +0900 Subject: [PATCH 06/17] docs: bind the Figma baseline to persisted product claims --- ...026-08-14-db-grounded-product-ux-design.md | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md index e55b4821c..9111c5e7b 100644 --- a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md +++ b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md @@ -4,7 +4,10 @@ **Date:** 2026-08-14 **Figma:** `https://www.figma.com/design/UpjgFQEu4u2Kr2hmyorAqe` **Canonical Figma page:** `DB-grounded UX` -**Superseded concepts:** `Archive — superseded drafts` +**Canonical Figma page node:** `9:2` +**Superseded concepts:** `Archive — superseded drafts` +**Archive Figma page node:** `0:1` +**Last contract synchronization:** `2026-08-15` ## Goal @@ -24,6 +27,25 @@ The design does not make the relational schema visible merely for debugging. It uses the schema to determine which nouns, actions, cardinalities, and explanations are truthful. +## Figma synchronization contract + +The six-screen `DB-grounded UX` page is the only implementation baseline. The +older three-screen concept and every deferred surface remain on +`Archive — superseded drafts` so a visually persuasive mockup cannot silently +become a product claim. + +- Graph change history, the PROV-O product explorer, and access audit remain archived + until normalized persistence, authorized APIs, and regression contracts make + each surface truthful. +- The canonical role vocabulary is `viewer` and `admin`; Figma does not fabricate an `Analyst` role. +- Direct lineage remains a plausible parent-child reconstruction, never a causal claim. +- The Records, Record Detail, Entity Catalog, Calendar and Reports, Accounts and + Access, and Roles and read-only System Policy screens stay synchronized with + the cardinality, authorization, evidence, and accessibility requirements in + this document. +- Any Figma change that alters a noun, action, relation, role, or state must + update this specification and its contract tests in the same pull request. + ## Primary invariant: DB cardinality is the interaction contract The interface must preserve the following independent relationships. From 730fcc34f35238a79062e34e5903b5bc6388ced4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:47:34 +0900 Subject: [PATCH 07/17] test: format the Figma synchronization contract --- tests/test_figma_contract_sync.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_figma_contract_sync.py b/tests/test_figma_contract_sync.py index e25d10f9b..0f0553524 100644 --- a/tests/test_figma_contract_sync.py +++ b/tests/test_figma_contract_sync.py @@ -26,8 +26,17 @@ def test_figma_contract_archives_unsupported_product_claims() -> None: """Attractive unmodeled screens stay archived rather than becoming claims.""" content = _SPEC.read_text(encoding="utf-8") for statement in ( - "Graph change history, the PROV-O product explorer, and access audit remain archived", - "The canonical role vocabulary is `viewer` and `admin`; Figma does not fabricate an `Analyst` role.", - "Direct lineage remains a plausible parent-child reconstruction, never a causal claim.", + ( + "Graph change history, the PROV-O product explorer, and access audit " + "remain archived" + ), + ( + "The canonical role vocabulary is `viewer` and `admin`; Figma does " + "not fabricate an `Analyst` role." + ), + ( + "Direct lineage remains a plausible parent-child reconstruction, " + "never a causal claim." + ), ): assert statement in content From ea880fe4ca6d48dba0d6492e321c7d54f8eadc41 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:56:56 +0900 Subject: [PATCH 08/17] test(red): require actionable Figma helper copy --- tests/test_figma_contract_sync.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_figma_contract_sync.py b/tests/test_figma_contract_sync.py index 0f0553524..61ae34543 100644 --- a/tests/test_figma_contract_sync.py +++ b/tests/test_figma_contract_sync.py @@ -40,3 +40,17 @@ def test_figma_contract_archives_unsupported_product_claims() -> None: ), ): assert statement in content + + +def test_figma_helper_copy_is_actionable_and_semantically_consistent() -> None: + """Customer copy and synthetic examples must preserve the product contract.""" + content = _SPEC.read_text(encoding="utf-8") + assert ( + "Canonical helper copy directs customers to open evidence, records, " + "actors, tickets, or reports." + in content + ) + assert ( + "Synthetic report grouping labels must match the persisted grouping kind." + in content + ) From e7c3a2d4cff923646b6d0fcd48fd796aa047baaf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:59:17 +0900 Subject: [PATCH 09/17] docs: require actionable Figma copy and coherent examples --- .../specs/2026-08-14-db-grounded-product-ux-design.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md index 9111c5e7b..c2ae2b162 100644 --- a/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md +++ b/docs/superpowers/specs/2026-08-14-db-grounded-product-ux-design.md @@ -39,6 +39,8 @@ become a product claim. each surface truthful. - The canonical role vocabulary is `viewer` and `admin`; Figma does not fabricate an `Analyst` role. - Direct lineage remains a plausible parent-child reconstruction, never a causal claim. +- Canonical helper copy directs customers to open evidence, records, actors, tickets, or reports. +- Synthetic report grouping labels must match the persisted grouping kind. - The Records, Record Detail, Entity Catalog, Calendar and Reports, Accounts and Access, and Roles and read-only System Policy screens stay synchronized with the cardinality, authorization, evidence, and accessibility requirements in From 0aa3bbde0c76c03d28f5093996fcb14329272c52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:00:00 +0900 Subject: [PATCH 10/17] docs: record the accepted Figma synchronization --- CHANGELOG.d/hourly-db-grounded-commercialization-loop.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md b/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md index a2d61a1de..2aac78991 100644 --- a/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md +++ b/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md @@ -18,3 +18,8 @@ Catalog, Calendar, Reports, Accounts, Roles, and read-only system-policy interactions, with APA 7th references for WCAG 2.2, WAI-ARIA 1.2, ISO/IEC 25010:2023, ISO/IEC 40500:2025, and NIST zero-trust controls. +- The accepted Figma baseline is now bound to canonical and archive page IDs. + Unsupported graph history, buyer-facing provenance, and access-audit claims + remain archived, while helper copy directs customers to evidence-backed next + actions and synthetic report labels remain consistent with their grouping + kind. From ef4def1144e8937308dd2bc931c158dc38bf64ec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:14:56 +0900 Subject: [PATCH 11/17] test(red): require central single-writer PR governance --- .../test_hourly_commercialization_workflow.py | 85 +++++++++---------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/tests/test_hourly_commercialization_workflow.py b/tests/test_hourly_commercialization_workflow.py index e1cc8904e..1bca70c5d 100644 --- a/tests/test_hourly_commercialization_workflow.py +++ b/tests/test_hourly_commercialization_workflow.py @@ -1,9 +1,9 @@ -"""Contract tests for the hourly DB-grounded commercialization workflow. +"""Contracts for the centrally governed hourly DB-grounded product-gap loop. -The workflow is security-sensitive production automation. These tests keep its -review-first queue discipline, NVIDIA-only OpenCode execution, test-first -authoring, sandboxed validation, and one-PR mutation boundary visible in code -review instead of relying on prose. +The repository workflow is security-sensitive production automation. These +tests keep central PR governance single-writer, NVIDIA-only OpenCode execution, +test-first authoring, sandboxed validation, and the one-PR mutation boundary +visible in code review instead of relying on prose. """ from __future__ import annotations @@ -59,54 +59,47 @@ def _implementation_path_classifiers() -> dict[str, object]: def test_schedule_runs_hourly_without_cancelling_long_accuracy_work() -> None: """The heartbeat is hourly while long OpenCode runs queue instead of being killed.""" assert '- cron: "23 * * * *"' in _WORKFLOW - assert "group: lineageweave-hourly-commercialization-loop" in _WORKFLOW + assert "group: lineageweave-hourly-product-gap-loop" in _WORKFLOW assert "cancel-in-progress: false" in _WORKFLOW assert "timeout-minutes: 180" in _WORKFLOW -def test_review_fix_check_merge_loop_precedes_new_development() -> None: - """Every run inspects, repairs, and revalidates PRs before opening new work.""" - assert _WORKFLOW.index(" inspect-pr-queue:") < _WORKFLOW.index( - " repair-review-feedback:" +def test_central_governance_remains_the_only_pr_writer_and_merger() -> None: + """The repository heartbeat may create one PR but never duplicate PR governance.""" + forbidden = ( + "pr-review-merge-scheduler.yml", + "pr-review-fix-scheduler.yml", + "inspect-pr-queue", + "repair-review-feedback", + "revalidate-pr-queue", + "review_dispatch_limit:", + "branch_update_limit:", + "max_dispatches:", + "merge_mode:", + "enable_auto_merge:", + "update_branches:", + "pull-requests: write", + "actions: write", ) - assert _WORKFLOW.index(" repair-review-feedback:") < _WORKFLOW.index( - " revalidate-pr-queue:" - ) - assert _WORKFLOW.index(" revalidate-pr-queue:") < _WORKFLOW.index( - " develop-next-product-gap:" - ) - develop = _job_text("develop-next-product-gap") - assert "needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue]" in develop - assert "open_pr_count" in develop - assert "An open pull request owns the queue" in develop - + for marker in forbidden: + assert marker not in _WORKFLOW -def test_central_review_and_repair_workflows_are_immutable_pins() -> None: - """Reusable governance workflows must use exact commits, never moving refs.""" - expected = ( - "ContextualWisdomLab/.github/.github/workflows/" - "pr-review-merge-scheduler.yml@" - "6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba" - ) - repair = ( - "ContextualWisdomLab/.github/.github/workflows/" - "pr-review-fix-scheduler.yml@" - "6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba" - ) - assert _WORKFLOW.count(expected) == 2 - assert repair in _WORKFLOW - assert "@main" not in "\n".join( - line for line in _WORKFLOW.splitlines() if "uses: ContextualWisdomLab/.github" in line - ) + operations = _OPERATIONS_PATH.read_text(encoding="utf-8") + assert "central `.github` scheduler is the only PR review, repair, branch-update, and merge writer" in operations + assert "every 15 minutes" in operations -def test_scheduler_is_configured_to_exhaust_the_pr_queue() -> None: - """Review and branch-update budgets do not strand additional eligible PRs.""" - assert _WORKFLOW.count('review_dispatch_limit: "-1"') == 2 - assert _WORKFLOW.count('branch_update_limit: "-1"') == 2 - assert _WORKFLOW.count("merge_mode: direct_or_auto") == 2 - assert _WORKFLOW.count("enable_auto_merge: true") == 2 - assert 'max_dispatches: "50"' in _WORKFLOW +def test_product_development_is_gated_by_read_only_live_queue_state() -> None: + """A nonempty PR queue blocks authoring without mutating any existing PR.""" + develop = _job_text("develop-next-product-gap") + header = develop.split(" steps:\n", maxsplit=1)[0] + assert "needs:" not in header + assert "contents: read" in header + assert "pull-requests: read" in header + assert "id-token: write" in header + assert "open_pr_count" in develop + assert "Central governance owns every open pull request" in develop + assert "eligible=false" in develop def test_product_agent_uses_only_nvidia_nim_and_pinned_opencode() -> None: @@ -267,7 +260,7 @@ def test_networkless_validation_keeps_real_service_evidence_in_exact_head_ci() - assert "ordinary exact-head pull-request checks" in operations -def test_mutation_rechecks_single_writer_and_base_freshness() -> None: +def test_mutation_rechecks_queue_and_base_freshness() -> None: """A concurrent PR or moved main branch discards stale autonomous work.""" assert _WORKFLOW.count( 'gh api "/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1"' From a6ab57ffcbd8143ad74f9027ea7827e555ac8cf5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:17:22 +0900 Subject: [PATCH 12/17] ci: run the deterministic PR 76 governance repair --- .../pr76-central-governance-repair.yml | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 .github/workflows/pr76-central-governance-repair.yml diff --git a/.github/workflows/pr76-central-governance-repair.yml b/.github/workflows/pr76-central-governance-repair.yml new file mode 100644 index 000000000..e8e12a870 --- /dev/null +++ b/.github/workflows/pr76-central-governance-repair.yml @@ -0,0 +1,265 @@ +name: Repair PR 76 central governance boundary + +on: + push: + branches: + - automation/hourly-db-grounded-commercialization-loop + paths: + - .github/workflows/pr76-central-governance-repair.yml + +permissions: {} + +concurrency: + group: pr76-central-governance-repair + cancel-in-progress: false + +jobs: + repair: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + permissions: + contents: write + env: + EXPECTED_PARENT_SHA: ef4def1144e8937308dd2bc931c158dc38bf64ec + steps: + - name: Check out the exact PR branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + ref: automation/hourly-db-grounded-commercialization-loop + fetch-depth: 0 + persist-credentials: true + + - name: Reject stale or reordered execution + run: | + set -euo pipefail + test "$(git rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 + with: + python-version: "3.12" + + - name: Set up locked Python dependency manager + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 + with: + version: "0.11.28" + enable-cache: false + + - name: Set up Node + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 + with: + node-version: "24" + + - name: Install committed dependencies + run: | + set -euo pipefail + uv sync --frozen --extra dev --extra backend + corepack enable + pnpm --dir frontend install --frozen-lockfile + + - name: Prove the central-governance contract is red + run: | + set -euo pipefail + set +e + uv run --frozen python -m pytest -q \ + tests/test_hourly_commercialization_workflow.py \ + >/tmp/pr76-central-governance-red.log 2>&1 + status=$? + set -e + cat /tmp/pr76-central-governance-red.log + test "$status" -eq 1 + grep -Eq "central_governance|read_only_live_queue|schedule_runs_hourly" \ + /tmp/pr76-central-governance-red.log + + - name: Remove the competing PR writer and align operations evidence + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + + workflow_path = Path(".github/workflows/hourly-commercialization-loop.yml") + workflow = workflow_path.read_text(encoding="utf-8") + workflow = workflow.replace( + "name: Hourly LineageWeave Commercialization Loop", + "name: Hourly LineageWeave Product Gap Loop", + 1, + ).replace( + "group: lineageweave-hourly-commercialization-loop", + "group: lineageweave-hourly-product-gap-loop", + 1, + ) + jobs_start = workflow.index("jobs:\n inspect-pr-queue:") + product_start = workflow.index(" develop-next-product-gap:", jobs_start) + workflow = workflow[:jobs_start] + "jobs:\n" + workflow[product_start:] + old_header = """ develop-next-product-gap: + needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue] + if: >- + ${{ + always() && + needs.inspect-pr-queue.result == 'success' && + needs.repair-review-feedback.result == 'success' && + needs.revalidate-pr-queue.result == 'success' + }} + runs-on: ubuntu-24.04 +""" + new_header = """ develop-next-product-gap: + runs-on: ubuntu-24.04 +""" + if old_header not in workflow: + raise SystemExit("missing competing-writer job dependency header") + workflow = workflow.replace(old_header, new_header, 1) + old_message = ( + "An open pull request owns the queue; review, repair, checks, " + "and merge stay ahead of new development." + ) + new_message = ( + "Central governance owns every open pull request; product " + "development remains read-only and waits for the queue to reach zero." + ) + if old_message not in workflow: + raise SystemExit("missing queue-owner message") + workflow = workflow.replace(old_message, new_message, 1) + workflow = workflow.replace( + "python -m compileall -q lineageweave backend tests", + "uv run --frozen python -m compileall -q lineageweave backend tests", + 1, + ) + forbidden = ( + "pr-review-merge-scheduler.yml", + "pr-review-fix-scheduler.yml", + "pull-requests: write", + "actions: write", + "merge_mode:", + "enable_auto_merge:", + ) + for marker in forbidden: + if marker in workflow: + raise SystemExit(f"competing PR-governance marker remains: {marker}") + workflow_path.write_text(workflow, encoding="utf-8") + + operations_path = Path("docs/operations/hourly-commercialization-loop.md") + operations = operations_path.read_text(encoding="utf-8") + queue_start = operations.index("## Purpose\n") + cadence_start = operations.index("## Accuracy-first cadence\n") + replacement = """## Purpose + +The hourly repository workflow turns the approved DB-grounded Figma design into +one protected product increment only when the live LineageWeave pull-request +queue is empty. It never reviews, repairs, updates, approves, or merges an +existing pull request. + +The workflow file is +`.github/workflows/hourly-commercialization-loop.yml`. It runs at minute 23 of +every hour and can also be invoked manually. + +## Central governance and queue policy + +The central `.github` scheduler is the only PR review, repair, branch-update, and merge writer. +It runs the organization-wide sweep every 15 minutes and reacts to PR, review, +and required-workflow events. LineageWeave does not install a second merger or +call the central reusable writer from its own schedule. + +```mermaid +flowchart TD + A[Hourly product trigger] --> B[Read live open-PR count] + B --> C{Any open PR?} + C -->|yes| D[Exit without mutating the queue] + C -->|no| E[Select one buyer-visible DB-grounded gap] + E --> F[Write design supplement and failing test] + F --> G[Implement one vertical slice] + G --> H[Validate in isolated copy without network] + H --> I{Queue and main unchanged?} + I -->|no| J[Discard stale proposal] + I -->|yes| K[Open exactly one PR] + K --> L[Central governance reviews and merges] +``` + +The repository workflow receives only read access to pull-request inventory. +After validation and repeated queue/base checks, it may exchange the existing +OIDC credential for a short-lived app token that pushes one generated branch +and opens one PR. It cannot approve, update, or merge that PR. + +""" + operations = operations[:queue_start] + replacement + operations[cadence_start:] + operations = operations.replace( + "The product job has a 180-minute budget. It starts only after all three queue\n" + "jobs succeed and no open pull request remains.", + "The product job has a 180-minute budget. It starts only when read-only live\n" + "inspection finds no open pull request; central governance continues independently.", + 1, + ) + operations = operations.replace( + "Review wait time is not a blocker. Subsequent hourly invocations continue\n" + "repairing and revalidating the queue but do not create another product PR.", + "Review wait time is not a blocker. The central scheduler continues reviewing,\n" + "repairing, and revalidating the queue; the repository heartbeat exits without\n" + "creating another product PR while any pull request remains open.", + 1, + ) + operations = operations.replace( + "The permanent contract tests in\n" + "`tests/test_hourly_commercialization_workflow.py` verify the schedule,\n" + "governance pins, NVIDIA-only model path, credential removal, red/green\n" + "discipline, protected paths, isolated validation, stale-work checks, and\n" + "one-PR/no-self-merge boundary.\n\n" + "The central scheduler remains independently active. This repository workflow\n" + "adds a LineageWeave-specific hourly heartbeat and product-gap generator; it\n" + "does not duplicate the central scheduler's implementation.", + "The permanent contract tests in\n" + "`tests/test_hourly_commercialization_workflow.py` verify the schedule, central\n" + "single-writer boundary, read-only live queue gate, NVIDIA-only model path,\n" + "credential removal, red/green discipline, protected paths, isolated\n" + "validation, stale-work checks, and one-PR/no-self-merge boundary.\n\n" + "The central scheduler remains independently active every 15 minutes. This\n" + "repository workflow contributes only the LineageWeave product-gap heartbeat.", + 1, + ) + operations_path.write_text(operations, encoding="utf-8") + + changelog_path = Path("CHANGELOG.d/hourly-db-grounded-commercialization-loop.md") + changelog = changelog_path.read_text(encoding="utf-8") + changelog = changelog.replace( + "- An hourly, review-first commercialization workflow now drains open pull\n" + " requests through current-head review, feedback repair, check revalidation,\n" + " branch refresh, and protected merge before creating more work.", + "- An hourly product-gap workflow now reads the live pull-request queue and\n" + " exits without mutation whenever an open PR exists. The organization-central\n" + " scheduler remains the only review, repair, branch-update, and merge writer.", + 1, + ) + changelog = changelog.replace( + "- Permanent workflow-contract tests bind the schedule, immutable central\n" + " governance references, credential removal, no-Copilot rule, test-first\n" + " evidence, protected paths, stale-work checks, and no-self-merge boundary.", + "- Permanent workflow-contract tests bind the hourly schedule, central\n" + " single-writer boundary, read-only queue gate, credential removal,\n" + " no-Copilot rule, test-first evidence, protected paths, stale-work checks,\n" + " and no-self-merge boundary.", + 1, + ) + changelog_path.write_text(changelog, encoding="utf-8") + PY + + - name: Verify the repaired governance and product contracts + run: | + set -euo pipefail + uv run --frozen python -m pytest -q \ + tests/test_hourly_commercialization_workflow.py \ + tests/test_figma_contract_sync.py + uv run --frozen python -m pytest -q + uv run --frozen python -m compileall -q lineageweave backend tests + pnpm --dir frontend run lint + pnpm --dir frontend run test + pnpm --dir frontend run build + git diff --check + + - name: Commit the reviewed central-governance repair + run: | + set -euo pipefail + rm .github/workflows/pr76-central-governance-repair.yml + git add -A + git diff --cached --check + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "fix(ci): keep PR governance organization-central" + git push origin HEAD:automation/hourly-db-grounded-commercialization-loop From db483a70fee0ed1cc9c66e6ab58264d2c1bd8cf7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:19:08 +0900 Subject: [PATCH 13/17] fix(ci): make the PR 76 repair workflow parseable --- .../pr76-central-governance-repair.yml | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pr76-central-governance-repair.yml b/.github/workflows/pr76-central-governance-repair.yml index e8e12a870..e404d24d5 100644 --- a/.github/workflows/pr76-central-governance-repair.yml +++ b/.github/workflows/pr76-central-governance-repair.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: write env: - EXPECTED_PARENT_SHA: ef4def1144e8937308dd2bc931c158dc38bf64ec + EXPECTED_PARENT_SHA: a6ab57ffcbd8143ad74f9027ea7827e555ac8cf5 steps: - name: Check out the exact PR branch uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 @@ -91,23 +91,13 @@ jobs: jobs_start = workflow.index("jobs:\n inspect-pr-queue:") product_start = workflow.index(" develop-next-product-gap:", jobs_start) workflow = workflow[:jobs_start] + "jobs:\n" + workflow[product_start:] - old_header = """ develop-next-product-gap: - needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue] - if: >- - ${{ - always() && - needs.inspect-pr-queue.result == 'success' && - needs.repair-review-feedback.result == 'success' && - needs.revalidate-pr-queue.result == 'success' - }} - runs-on: ubuntu-24.04 -""" - new_header = """ develop-next-product-gap: - runs-on: ubuntu-24.04 -""" - if old_header not in workflow: - raise SystemExit("missing competing-writer job dependency header") - workflow = workflow.replace(old_header, new_header, 1) + product_start = workflow.index(" develop-next-product-gap:") + runs_on_start = workflow.index(" runs-on: ubuntu-24.04", product_start) + workflow = ( + workflow[:product_start] + + " develop-next-product-gap:\n" + + workflow[runs_on_start:] + ) old_message = ( "An open pull request owns the queue; review, repair, checks, " "and merge stay ahead of new development." From 45459a3c8b845a3033ef94ebfe94b3b7b77856a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:21:10 +0900 Subject: [PATCH 14/17] ci: add deterministic PR 76 governance repair script --- .../scripts/pr76_central_governance_repair.py | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 .github/scripts/pr76_central_governance_repair.py diff --git a/.github/scripts/pr76_central_governance_repair.py b/.github/scripts/pr76_central_governance_repair.py new file mode 100644 index 000000000..ea6e9e561 --- /dev/null +++ b/.github/scripts/pr76_central_governance_repair.py @@ -0,0 +1,174 @@ +"""Deterministically remove the competing PR writer from LineageWeave PR 76.""" + +from __future__ import annotations + +from pathlib import Path + + +def repair_workflow() -> None: + """Retain the product-gap generator while delegating PR governance centrally.""" + path = Path(".github/workflows/hourly-commercialization-loop.yml") + content = path.read_text(encoding="utf-8") + content = content.replace( + "name: Hourly LineageWeave Commercialization Loop", + "name: Hourly LineageWeave Product Gap Loop", + 1, + ).replace( + "group: lineageweave-hourly-commercialization-loop", + "group: lineageweave-hourly-product-gap-loop", + 1, + ) + jobs_start = content.index("jobs:\n inspect-pr-queue:") + product_start = content.index(" develop-next-product-gap:", jobs_start) + content = content[:jobs_start] + "jobs:\n" + content[product_start:] + product_start = content.index(" develop-next-product-gap:") + runs_on_start = content.index(" runs-on: ubuntu-24.04", product_start) + content = ( + content[:product_start] + + " develop-next-product-gap:\n" + + content[runs_on_start:] + ) + old_message = ( + "An open pull request owns the queue; review, repair, checks, " + "and merge stay ahead of new development." + ) + new_message = ( + "Central governance owns every open pull request; product development " + "remains read-only and waits for the queue to reach zero." + ) + if old_message not in content: + raise SystemExit("missing queue-owner message") + content = content.replace(old_message, new_message, 1) + content = content.replace( + "python -m compileall -q lineageweave backend tests", + "uv run --frozen python -m compileall -q lineageweave backend tests", + 1, + ) + forbidden = ( + "pr-review-merge-scheduler.yml", + "pr-review-fix-scheduler.yml", + "pull-requests: write", + "actions: write", + "merge_mode:", + "enable_auto_merge:", + ) + for marker in forbidden: + if marker in content: + raise SystemExit(f"competing PR-governance marker remains: {marker}") + path.write_text(content, encoding="utf-8") + + +def repair_operations() -> None: + """Describe the central scheduler as the only PR governance writer.""" + path = Path("docs/operations/hourly-commercialization-loop.md") + content = path.read_text(encoding="utf-8") + start = content.index("## Purpose\n") + cadence = content.index("## Accuracy-first cadence\n") + replacement = ( + "## Purpose\n\n" + "The hourly repository workflow turns the approved DB-grounded Figma design into\n" + "one protected product increment only when the live LineageWeave pull-request\n" + "queue is empty. It never reviews, repairs, updates, approves, or merges an\n" + "existing pull request.\n\n" + "The workflow file is\n" + "`.github/workflows/hourly-commercialization-loop.yml`. It runs at minute 23 of\n" + "every hour and can also be invoked manually.\n\n" + "## Central governance and queue policy\n\n" + "The central `.github` scheduler is the only PR review, repair, branch-update, and merge writer.\n" + "It runs the organization-wide sweep every 15 minutes and reacts to PR, review,\n" + "and required-workflow events. LineageWeave does not install a second merger or\n" + "call the central reusable writer from its own schedule.\n\n" + "```mermaid\n" + "flowchart TD\n" + " A[Hourly product trigger] --> B[Read live open-PR count]\n" + " B --> C{Any open PR?}\n" + " C -->|yes| D[Exit without mutating the queue]\n" + " C -->|no| E[Select one buyer-visible DB-grounded gap]\n" + " E --> F[Write design supplement and failing test]\n" + " F --> G[Implement one vertical slice]\n" + " G --> H[Validate in isolated copy without network]\n" + " H --> I{Queue and main unchanged?}\n" + " I -->|no| J[Discard stale proposal]\n" + " I -->|yes| K[Open exactly one PR]\n" + " K --> L[Central governance reviews and merges]\n" + "```\n\n" + "The repository workflow receives only read access to pull-request inventory.\n" + "After validation and repeated queue/base checks, it may exchange the existing\n" + "OIDC credential for a short-lived app token that pushes one generated branch\n" + "and opens one PR. It cannot approve, update, or merge that PR.\n\n" + ) + content = content[:start] + replacement + content[cadence:] + content = content.replace( + "The product job has a 180-minute budget. It starts only after all three queue\n" + "jobs succeed and no open pull request remains.", + "The product job has a 180-minute budget. It starts only when read-only live\n" + "inspection finds no open pull request; central governance continues independently.", + 1, + ) + content = content.replace( + "Review wait time is not a blocker. Subsequent hourly invocations continue\n" + "repairing and revalidating the queue but do not create another product PR.", + "Review wait time is not a blocker. The central scheduler continues reviewing,\n" + "repairing, and revalidating the queue; the repository heartbeat exits without\n" + "creating another product PR while any pull request remains open.", + 1, + ) + old_evidence = ( + "The permanent contract tests in\n" + "`tests/test_hourly_commercialization_workflow.py` verify the schedule,\n" + "governance pins, NVIDIA-only model path, credential removal, red/green\n" + "discipline, protected paths, isolated validation, stale-work checks, and\n" + "one-PR/no-self-merge boundary.\n\n" + "The central scheduler remains independently active. This repository workflow\n" + "adds a LineageWeave-specific hourly heartbeat and product-gap generator; it\n" + "does not duplicate the central scheduler's implementation." + ) + new_evidence = ( + "The permanent contract tests in\n" + "`tests/test_hourly_commercialization_workflow.py` verify the schedule, central\n" + "single-writer boundary, read-only live queue gate, NVIDIA-only model path,\n" + "credential removal, red/green discipline, protected paths, isolated\n" + "validation, stale-work checks, and one-PR/no-self-merge boundary.\n\n" + "The central scheduler remains independently active every 15 minutes. This\n" + "repository workflow contributes only the LineageWeave product-gap heartbeat." + ) + if old_evidence not in content: + raise SystemExit("missing operations evidence anchor") + path.write_text(content.replace(old_evidence, new_evidence, 1), encoding="utf-8") + + +def repair_changelog() -> None: + """Record the central single-writer and read-only queue boundary.""" + path = Path("CHANGELOG.d/hourly-db-grounded-commercialization-loop.md") + content = path.read_text(encoding="utf-8") + content = content.replace( + "- An hourly, review-first commercialization workflow now drains open pull\n" + " requests through current-head review, feedback repair, check revalidation,\n" + " branch refresh, and protected merge before creating more work.", + "- An hourly product-gap workflow now reads the live pull-request queue and\n" + " exits without mutation whenever an open PR exists. The organization-central\n" + " scheduler remains the only review, repair, branch-update, and merge writer.", + 1, + ) + content = content.replace( + "- Permanent workflow-contract tests bind the schedule, immutable central\n" + " governance references, credential removal, no-Copilot rule, test-first\n" + " evidence, protected paths, stale-work checks, and no-self-merge boundary.", + "- Permanent workflow-contract tests bind the hourly schedule, central\n" + " single-writer boundary, read-only queue gate, credential removal,\n" + " no-Copilot rule, test-first evidence, protected paths, stale-work checks,\n" + " and no-self-merge boundary.", + 1, + ) + path.write_text(content, encoding="utf-8") + + +def main() -> None: + """Apply every deterministic governance repair.""" + repair_workflow() + repair_operations() + repair_changelog() + + +if __name__ == "__main__": + main() From ca24ba4e8ac38f30b25064e89eb2c713330cd5c0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:21:44 +0900 Subject: [PATCH 15/17] fix(ci): run PR 76 governance repair from a checked script --- .../pr76-central-governance-repair.yml | 161 +----------------- 1 file changed, 4 insertions(+), 157 deletions(-) diff --git a/.github/workflows/pr76-central-governance-repair.yml b/.github/workflows/pr76-central-governance-repair.yml index e404d24d5..b44561abe 100644 --- a/.github/workflows/pr76-central-governance-repair.yml +++ b/.github/workflows/pr76-central-governance-repair.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: write env: - EXPECTED_PARENT_SHA: a6ab57ffcbd8143ad74f9027ea7827e555ac8cf5 + EXPECTED_PARENT_SHA: 45459a3c8b845a3033ef94ebfe94b3b7b77856a5 steps: - name: Check out the exact PR branch uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 @@ -71,164 +71,10 @@ jobs: grep -Eq "central_governance|read_only_live_queue|schedule_runs_hourly" \ /tmp/pr76-central-governance-red.log - - name: Remove the competing PR writer and align operations evidence + - name: Apply the deterministic central-governance repair run: | set -euo pipefail - python - <<'PY' - from pathlib import Path - - workflow_path = Path(".github/workflows/hourly-commercialization-loop.yml") - workflow = workflow_path.read_text(encoding="utf-8") - workflow = workflow.replace( - "name: Hourly LineageWeave Commercialization Loop", - "name: Hourly LineageWeave Product Gap Loop", - 1, - ).replace( - "group: lineageweave-hourly-commercialization-loop", - "group: lineageweave-hourly-product-gap-loop", - 1, - ) - jobs_start = workflow.index("jobs:\n inspect-pr-queue:") - product_start = workflow.index(" develop-next-product-gap:", jobs_start) - workflow = workflow[:jobs_start] + "jobs:\n" + workflow[product_start:] - product_start = workflow.index(" develop-next-product-gap:") - runs_on_start = workflow.index(" runs-on: ubuntu-24.04", product_start) - workflow = ( - workflow[:product_start] - + " develop-next-product-gap:\n" - + workflow[runs_on_start:] - ) - old_message = ( - "An open pull request owns the queue; review, repair, checks, " - "and merge stay ahead of new development." - ) - new_message = ( - "Central governance owns every open pull request; product " - "development remains read-only and waits for the queue to reach zero." - ) - if old_message not in workflow: - raise SystemExit("missing queue-owner message") - workflow = workflow.replace(old_message, new_message, 1) - workflow = workflow.replace( - "python -m compileall -q lineageweave backend tests", - "uv run --frozen python -m compileall -q lineageweave backend tests", - 1, - ) - forbidden = ( - "pr-review-merge-scheduler.yml", - "pr-review-fix-scheduler.yml", - "pull-requests: write", - "actions: write", - "merge_mode:", - "enable_auto_merge:", - ) - for marker in forbidden: - if marker in workflow: - raise SystemExit(f"competing PR-governance marker remains: {marker}") - workflow_path.write_text(workflow, encoding="utf-8") - - operations_path = Path("docs/operations/hourly-commercialization-loop.md") - operations = operations_path.read_text(encoding="utf-8") - queue_start = operations.index("## Purpose\n") - cadence_start = operations.index("## Accuracy-first cadence\n") - replacement = """## Purpose - -The hourly repository workflow turns the approved DB-grounded Figma design into -one protected product increment only when the live LineageWeave pull-request -queue is empty. It never reviews, repairs, updates, approves, or merges an -existing pull request. - -The workflow file is -`.github/workflows/hourly-commercialization-loop.yml`. It runs at minute 23 of -every hour and can also be invoked manually. - -## Central governance and queue policy - -The central `.github` scheduler is the only PR review, repair, branch-update, and merge writer. -It runs the organization-wide sweep every 15 minutes and reacts to PR, review, -and required-workflow events. LineageWeave does not install a second merger or -call the central reusable writer from its own schedule. - -```mermaid -flowchart TD - A[Hourly product trigger] --> B[Read live open-PR count] - B --> C{Any open PR?} - C -->|yes| D[Exit without mutating the queue] - C -->|no| E[Select one buyer-visible DB-grounded gap] - E --> F[Write design supplement and failing test] - F --> G[Implement one vertical slice] - G --> H[Validate in isolated copy without network] - H --> I{Queue and main unchanged?} - I -->|no| J[Discard stale proposal] - I -->|yes| K[Open exactly one PR] - K --> L[Central governance reviews and merges] -``` - -The repository workflow receives only read access to pull-request inventory. -After validation and repeated queue/base checks, it may exchange the existing -OIDC credential for a short-lived app token that pushes one generated branch -and opens one PR. It cannot approve, update, or merge that PR. - -""" - operations = operations[:queue_start] + replacement + operations[cadence_start:] - operations = operations.replace( - "The product job has a 180-minute budget. It starts only after all three queue\n" - "jobs succeed and no open pull request remains.", - "The product job has a 180-minute budget. It starts only when read-only live\n" - "inspection finds no open pull request; central governance continues independently.", - 1, - ) - operations = operations.replace( - "Review wait time is not a blocker. Subsequent hourly invocations continue\n" - "repairing and revalidating the queue but do not create another product PR.", - "Review wait time is not a blocker. The central scheduler continues reviewing,\n" - "repairing, and revalidating the queue; the repository heartbeat exits without\n" - "creating another product PR while any pull request remains open.", - 1, - ) - operations = operations.replace( - "The permanent contract tests in\n" - "`tests/test_hourly_commercialization_workflow.py` verify the schedule,\n" - "governance pins, NVIDIA-only model path, credential removal, red/green\n" - "discipline, protected paths, isolated validation, stale-work checks, and\n" - "one-PR/no-self-merge boundary.\n\n" - "The central scheduler remains independently active. This repository workflow\n" - "adds a LineageWeave-specific hourly heartbeat and product-gap generator; it\n" - "does not duplicate the central scheduler's implementation.", - "The permanent contract tests in\n" - "`tests/test_hourly_commercialization_workflow.py` verify the schedule, central\n" - "single-writer boundary, read-only live queue gate, NVIDIA-only model path,\n" - "credential removal, red/green discipline, protected paths, isolated\n" - "validation, stale-work checks, and one-PR/no-self-merge boundary.\n\n" - "The central scheduler remains independently active every 15 minutes. This\n" - "repository workflow contributes only the LineageWeave product-gap heartbeat.", - 1, - ) - operations_path.write_text(operations, encoding="utf-8") - - changelog_path = Path("CHANGELOG.d/hourly-db-grounded-commercialization-loop.md") - changelog = changelog_path.read_text(encoding="utf-8") - changelog = changelog.replace( - "- An hourly, review-first commercialization workflow now drains open pull\n" - " requests through current-head review, feedback repair, check revalidation,\n" - " branch refresh, and protected merge before creating more work.", - "- An hourly product-gap workflow now reads the live pull-request queue and\n" - " exits without mutation whenever an open PR exists. The organization-central\n" - " scheduler remains the only review, repair, branch-update, and merge writer.", - 1, - ) - changelog = changelog.replace( - "- Permanent workflow-contract tests bind the schedule, immutable central\n" - " governance references, credential removal, no-Copilot rule, test-first\n" - " evidence, protected paths, stale-work checks, and no-self-merge boundary.", - "- Permanent workflow-contract tests bind the hourly schedule, central\n" - " single-writer boundary, read-only queue gate, credential removal,\n" - " no-Copilot rule, test-first evidence, protected paths, stale-work checks,\n" - " and no-self-merge boundary.", - 1, - ) - changelog_path.write_text(changelog, encoding="utf-8") - PY + python .github/scripts/pr76_central_governance_repair.py - name: Verify the repaired governance and product contracts run: | @@ -246,6 +92,7 @@ and opens one PR. It cannot approve, update, or merge that PR. - name: Commit the reviewed central-governance repair run: | set -euo pipefail + rm .github/scripts/pr76_central_governance_repair.py rm .github/workflows/pr76-central-governance-repair.yml git add -A git diff --cached --check From 8239c51434983b5435c4eb6d12d6335721c7fa79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:24:50 +0900 Subject: [PATCH 16/17] fix(ci): pin pnpm for the PR 76 governance repair --- .github/workflows/pr76-central-governance-repair.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr76-central-governance-repair.yml b/.github/workflows/pr76-central-governance-repair.yml index b44561abe..4408f6835 100644 --- a/.github/workflows/pr76-central-governance-repair.yml +++ b/.github/workflows/pr76-central-governance-repair.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: write env: - EXPECTED_PARENT_SHA: 45459a3c8b845a3033ef94ebfe94b3b7b77856a5 + EXPECTED_PARENT_SHA: ca24ba4e8ac38f30b25064e89eb2c713330cd5c0 steps: - name: Check out the exact PR branch uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 @@ -55,6 +55,7 @@ jobs: set -euo pipefail uv sync --frozen --extra dev --extra backend corepack enable + corepack install --global pnpm@9.15.9 pnpm --dir frontend install --frozen-lockfile - name: Prove the central-governance contract is red From b81f29c3825f7ffc6680042c789a689900a7c7d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 07:27:04 +0000 Subject: [PATCH 17/17] fix(ci): keep PR governance organization-central --- .../scripts/pr76_central_governance_repair.py | 174 ------------------ .../hourly-commercialization-loop.yml | 75 +------- .../pr76-central-governance-repair.yml | 103 ----------- ...urly-db-grounded-commercialization-loop.md | 13 +- .../hourly-commercialization-loop.md | 70 +++---- 5 files changed, 47 insertions(+), 388 deletions(-) delete mode 100644 .github/scripts/pr76_central_governance_repair.py delete mode 100644 .github/workflows/pr76-central-governance-repair.yml diff --git a/.github/scripts/pr76_central_governance_repair.py b/.github/scripts/pr76_central_governance_repair.py deleted file mode 100644 index ea6e9e561..000000000 --- a/.github/scripts/pr76_central_governance_repair.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Deterministically remove the competing PR writer from LineageWeave PR 76.""" - -from __future__ import annotations - -from pathlib import Path - - -def repair_workflow() -> None: - """Retain the product-gap generator while delegating PR governance centrally.""" - path = Path(".github/workflows/hourly-commercialization-loop.yml") - content = path.read_text(encoding="utf-8") - content = content.replace( - "name: Hourly LineageWeave Commercialization Loop", - "name: Hourly LineageWeave Product Gap Loop", - 1, - ).replace( - "group: lineageweave-hourly-commercialization-loop", - "group: lineageweave-hourly-product-gap-loop", - 1, - ) - jobs_start = content.index("jobs:\n inspect-pr-queue:") - product_start = content.index(" develop-next-product-gap:", jobs_start) - content = content[:jobs_start] + "jobs:\n" + content[product_start:] - product_start = content.index(" develop-next-product-gap:") - runs_on_start = content.index(" runs-on: ubuntu-24.04", product_start) - content = ( - content[:product_start] - + " develop-next-product-gap:\n" - + content[runs_on_start:] - ) - old_message = ( - "An open pull request owns the queue; review, repair, checks, " - "and merge stay ahead of new development." - ) - new_message = ( - "Central governance owns every open pull request; product development " - "remains read-only and waits for the queue to reach zero." - ) - if old_message not in content: - raise SystemExit("missing queue-owner message") - content = content.replace(old_message, new_message, 1) - content = content.replace( - "python -m compileall -q lineageweave backend tests", - "uv run --frozen python -m compileall -q lineageweave backend tests", - 1, - ) - forbidden = ( - "pr-review-merge-scheduler.yml", - "pr-review-fix-scheduler.yml", - "pull-requests: write", - "actions: write", - "merge_mode:", - "enable_auto_merge:", - ) - for marker in forbidden: - if marker in content: - raise SystemExit(f"competing PR-governance marker remains: {marker}") - path.write_text(content, encoding="utf-8") - - -def repair_operations() -> None: - """Describe the central scheduler as the only PR governance writer.""" - path = Path("docs/operations/hourly-commercialization-loop.md") - content = path.read_text(encoding="utf-8") - start = content.index("## Purpose\n") - cadence = content.index("## Accuracy-first cadence\n") - replacement = ( - "## Purpose\n\n" - "The hourly repository workflow turns the approved DB-grounded Figma design into\n" - "one protected product increment only when the live LineageWeave pull-request\n" - "queue is empty. It never reviews, repairs, updates, approves, or merges an\n" - "existing pull request.\n\n" - "The workflow file is\n" - "`.github/workflows/hourly-commercialization-loop.yml`. It runs at minute 23 of\n" - "every hour and can also be invoked manually.\n\n" - "## Central governance and queue policy\n\n" - "The central `.github` scheduler is the only PR review, repair, branch-update, and merge writer.\n" - "It runs the organization-wide sweep every 15 minutes and reacts to PR, review,\n" - "and required-workflow events. LineageWeave does not install a second merger or\n" - "call the central reusable writer from its own schedule.\n\n" - "```mermaid\n" - "flowchart TD\n" - " A[Hourly product trigger] --> B[Read live open-PR count]\n" - " B --> C{Any open PR?}\n" - " C -->|yes| D[Exit without mutating the queue]\n" - " C -->|no| E[Select one buyer-visible DB-grounded gap]\n" - " E --> F[Write design supplement and failing test]\n" - " F --> G[Implement one vertical slice]\n" - " G --> H[Validate in isolated copy without network]\n" - " H --> I{Queue and main unchanged?}\n" - " I -->|no| J[Discard stale proposal]\n" - " I -->|yes| K[Open exactly one PR]\n" - " K --> L[Central governance reviews and merges]\n" - "```\n\n" - "The repository workflow receives only read access to pull-request inventory.\n" - "After validation and repeated queue/base checks, it may exchange the existing\n" - "OIDC credential for a short-lived app token that pushes one generated branch\n" - "and opens one PR. It cannot approve, update, or merge that PR.\n\n" - ) - content = content[:start] + replacement + content[cadence:] - content = content.replace( - "The product job has a 180-minute budget. It starts only after all three queue\n" - "jobs succeed and no open pull request remains.", - "The product job has a 180-minute budget. It starts only when read-only live\n" - "inspection finds no open pull request; central governance continues independently.", - 1, - ) - content = content.replace( - "Review wait time is not a blocker. Subsequent hourly invocations continue\n" - "repairing and revalidating the queue but do not create another product PR.", - "Review wait time is not a blocker. The central scheduler continues reviewing,\n" - "repairing, and revalidating the queue; the repository heartbeat exits without\n" - "creating another product PR while any pull request remains open.", - 1, - ) - old_evidence = ( - "The permanent contract tests in\n" - "`tests/test_hourly_commercialization_workflow.py` verify the schedule,\n" - "governance pins, NVIDIA-only model path, credential removal, red/green\n" - "discipline, protected paths, isolated validation, stale-work checks, and\n" - "one-PR/no-self-merge boundary.\n\n" - "The central scheduler remains independently active. This repository workflow\n" - "adds a LineageWeave-specific hourly heartbeat and product-gap generator; it\n" - "does not duplicate the central scheduler's implementation." - ) - new_evidence = ( - "The permanent contract tests in\n" - "`tests/test_hourly_commercialization_workflow.py` verify the schedule, central\n" - "single-writer boundary, read-only live queue gate, NVIDIA-only model path,\n" - "credential removal, red/green discipline, protected paths, isolated\n" - "validation, stale-work checks, and one-PR/no-self-merge boundary.\n\n" - "The central scheduler remains independently active every 15 minutes. This\n" - "repository workflow contributes only the LineageWeave product-gap heartbeat." - ) - if old_evidence not in content: - raise SystemExit("missing operations evidence anchor") - path.write_text(content.replace(old_evidence, new_evidence, 1), encoding="utf-8") - - -def repair_changelog() -> None: - """Record the central single-writer and read-only queue boundary.""" - path = Path("CHANGELOG.d/hourly-db-grounded-commercialization-loop.md") - content = path.read_text(encoding="utf-8") - content = content.replace( - "- An hourly, review-first commercialization workflow now drains open pull\n" - " requests through current-head review, feedback repair, check revalidation,\n" - " branch refresh, and protected merge before creating more work.", - "- An hourly product-gap workflow now reads the live pull-request queue and\n" - " exits without mutation whenever an open PR exists. The organization-central\n" - " scheduler remains the only review, repair, branch-update, and merge writer.", - 1, - ) - content = content.replace( - "- Permanent workflow-contract tests bind the schedule, immutable central\n" - " governance references, credential removal, no-Copilot rule, test-first\n" - " evidence, protected paths, stale-work checks, and no-self-merge boundary.", - "- Permanent workflow-contract tests bind the hourly schedule, central\n" - " single-writer boundary, read-only queue gate, credential removal,\n" - " no-Copilot rule, test-first evidence, protected paths, stale-work checks,\n" - " and no-self-merge boundary.", - 1, - ) - path.write_text(content, encoding="utf-8") - - -def main() -> None: - """Apply every deterministic governance repair.""" - repair_workflow() - repair_operations() - repair_changelog() - - -if __name__ == "__main__": - main() diff --git a/.github/workflows/hourly-commercialization-loop.yml b/.github/workflows/hourly-commercialization-loop.yml index 6be0a92b0..2c9441c50 100644 --- a/.github/workflows/hourly-commercialization-loop.yml +++ b/.github/workflows/hourly-commercialization-loop.yml @@ -1,4 +1,4 @@ -name: Hourly LineageWeave Commercialization Loop +name: Hourly LineageWeave Product Gap Loop on: schedule: @@ -9,78 +9,11 @@ permissions: contents: read concurrency: - group: lineageweave-hourly-commercialization-loop + group: lineageweave-hourly-product-gap-loop cancel-in-progress: false jobs: - inspect-pr-queue: - permissions: - actions: write - checks: read - contents: write - id-token: write - pull-requests: write - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-merge-scheduler.yml@6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba - with: - base_branch: main - max_prs: "50" - trigger_reviews: true - review_dispatch_limit: "-1" - branch_update_limit: "-1" - enable_auto_merge: true - merge_mode: direct_or_auto - update_branches: true - secrets: inherit - - repair-review-feedback: - needs: inspect-pr-queue - if: ${{ always() }} - permissions: - actions: write - contents: read - issues: write - pull-requests: read - statuses: read - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba - with: - target_repository: ContextualWisdomLab/LineageWeave - base_branch: main - max_prs: "50" - max_dispatches: "50" - retry_hours: "1" - canonical_ref: 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba - secrets: inherit - - revalidate-pr-queue: - needs: repair-review-feedback - if: ${{ always() }} - permissions: - actions: write - checks: read - contents: write - id-token: write - pull-requests: write - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-merge-scheduler.yml@6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba - with: - base_branch: main - max_prs: "50" - trigger_reviews: true - review_dispatch_limit: "-1" - branch_update_limit: "-1" - enable_auto_merge: true - merge_mode: direct_or_auto - update_branches: true - secrets: inherit - develop-next-product-gap: - needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue] - if: >- - ${{ - always() && - needs.inspect-pr-queue.result == 'success' && - needs.repair-review-feedback.result == 'success' && - needs.revalidate-pr-queue.result == 'success' - }} runs-on: ubuntu-24.04 timeout-minutes: 180 permissions: @@ -135,7 +68,7 @@ jobs: --jq 'length' )" if [ "$open_pr_count" -ne 0 ]; then - echo "An open pull request owns the queue; review, repair, checks, and merge stay ahead of new development." + echo "Central governance owns every open pull request; product development remains read-only and waits for the queue to reach zero." echo "eligible=false" >>"$GITHUB_OUTPUT" exit 0 fi @@ -207,7 +140,7 @@ jobs: pnpm --dir frontend run lint pnpm --dir frontend run test pnpm --dir frontend run build - python -m compileall -q lineageweave backend tests + uv run --frozen python -m compileall -q lineageweave backend tests - name: Install the pinned OpenCode CLI if: steps.gate.outputs.eligible == 'true' diff --git a/.github/workflows/pr76-central-governance-repair.yml b/.github/workflows/pr76-central-governance-repair.yml deleted file mode 100644 index 4408f6835..000000000 --- a/.github/workflows/pr76-central-governance-repair.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Repair PR 76 central governance boundary - -on: - push: - branches: - - automation/hourly-db-grounded-commercialization-loop - paths: - - .github/workflows/pr76-central-governance-repair.yml - -permissions: {} - -concurrency: - group: pr76-central-governance-repair - cancel-in-progress: false - -jobs: - repair: - runs-on: ubuntu-24.04 - timeout-minutes: 60 - permissions: - contents: write - env: - EXPECTED_PARENT_SHA: ca24ba4e8ac38f30b25064e89eb2c713330cd5c0 - steps: - - name: Check out the exact PR branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - with: - ref: automation/hourly-db-grounded-commercialization-loop - fetch-depth: 0 - persist-credentials: true - - - name: Reject stale or reordered execution - run: | - set -euo pipefail - test "$(git rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 - with: - python-version: "3.12" - - - name: Set up locked Python dependency manager - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 - with: - version: "0.11.28" - enable-cache: false - - - name: Set up Node - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 - with: - node-version: "24" - - - name: Install committed dependencies - run: | - set -euo pipefail - uv sync --frozen --extra dev --extra backend - corepack enable - corepack install --global pnpm@9.15.9 - pnpm --dir frontend install --frozen-lockfile - - - name: Prove the central-governance contract is red - run: | - set -euo pipefail - set +e - uv run --frozen python -m pytest -q \ - tests/test_hourly_commercialization_workflow.py \ - >/tmp/pr76-central-governance-red.log 2>&1 - status=$? - set -e - cat /tmp/pr76-central-governance-red.log - test "$status" -eq 1 - grep -Eq "central_governance|read_only_live_queue|schedule_runs_hourly" \ - /tmp/pr76-central-governance-red.log - - - name: Apply the deterministic central-governance repair - run: | - set -euo pipefail - python .github/scripts/pr76_central_governance_repair.py - - - name: Verify the repaired governance and product contracts - run: | - set -euo pipefail - uv run --frozen python -m pytest -q \ - tests/test_hourly_commercialization_workflow.py \ - tests/test_figma_contract_sync.py - uv run --frozen python -m pytest -q - uv run --frozen python -m compileall -q lineageweave backend tests - pnpm --dir frontend run lint - pnpm --dir frontend run test - pnpm --dir frontend run build - git diff --check - - - name: Commit the reviewed central-governance repair - run: | - set -euo pipefail - rm .github/scripts/pr76_central_governance_repair.py - rm .github/workflows/pr76-central-governance-repair.yml - git add -A - git diff --cached --check - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git commit -m "fix(ci): keep PR governance organization-central" - git push origin HEAD:automation/hourly-db-grounded-commercialization-loop diff --git a/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md b/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md index 2aac78991..12d72f327 100644 --- a/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md +++ b/CHANGELOG.d/hourly-db-grounded-commercialization-loop.md @@ -2,17 +2,18 @@ ## Added -- An hourly, review-first commercialization workflow now drains open pull - requests through current-head review, feedback repair, check revalidation, - branch refresh, and protected merge before creating more work. +- An hourly product-gap workflow now reads the live pull-request queue and + exits without mutation whenever an open PR exists. The organization-central + scheduler remains the only review, repair, branch-update, and merge writer. - When the pull-request queue is empty, a pinned OpenCode CLI using only `NVIDIA_NIM_API_KEY` selects one buyer-visible gap from the approved DB-grounded Figma design, writes a failing regression first, implements one bounded vertical slice, validates it in an unprivileged network-isolated copy, and opens exactly one protected pull request. -- Permanent workflow-contract tests bind the schedule, immutable central - governance references, credential removal, no-Copilot rule, test-first - evidence, protected paths, stale-work checks, and no-self-merge boundary. +- Permanent workflow-contract tests bind the hourly schedule, central + single-writer boundary, read-only queue gate, credential removal, + no-Copilot rule, test-first evidence, protected paths, stale-work checks, + and no-self-merge boundary. - Product design and doctoring documentation now records the truthful mapping from PostgreSQL cardinalities to Records, Lineage, Record Detail, Entity Catalog, Calendar, Reports, Accounts, Roles, and read-only system-policy diff --git a/docs/operations/hourly-commercialization-loop.md b/docs/operations/hourly-commercialization-loop.md index d9e636f94..078a9f209 100644 --- a/docs/operations/hourly-commercialization-loop.md +++ b/docs/operations/hourly-commercialization-loop.md @@ -2,39 +2,41 @@ ## Purpose -The hourly workflow turns the approved DB-grounded Figma design into protected, -reviewed product increments while keeping pull-request completion ahead of new -feature creation. +The hourly repository workflow turns the approved DB-grounded Figma design into +one protected product increment only when the live LineageWeave pull-request +queue is empty. It never reviews, repairs, updates, approves, or merges an +existing pull request. The workflow file is `.github/workflows/hourly-commercialization-loop.yml`. It runs at minute 23 of every hour and can also be invoked manually. -## Queue policy +## Central governance and queue policy -One pull request owns the development queue. +The central `.github` scheduler is the only PR review, repair, branch-update, and merge writer. +It runs the organization-wide sweep every 15 minutes and reacts to PR, review, +and required-workflow events. LineageWeave does not install a second merger or +call the central reusable writer from its own schedule. ```mermaid flowchart TD - A[Hourly trigger] --> B[Inspect every open PR] - B --> C[Dispatch current-head review where missing] - C --> D[Repair actionable review feedback] - D --> E[Revalidate checks and branch freshness] - E --> F{Open PR remains?} - F -->|yes| A - F -->|no| G[Select one buyer-visible DB-grounded gap] - G --> H[Write design supplement and failing test] - H --> I[Implement one vertical slice] - I --> J[Validate in isolated copy without network] - J --> K{Queue and main unchanged?} - K -->|no| L[Discard stale proposal] - K -->|yes| M[Open exactly one PR] - M --> A + A[Hourly product trigger] --> B[Read live open-PR count] + B --> C{Any open PR?} + C -->|yes| D[Exit without mutating the queue] + C -->|no| E[Select one buyer-visible DB-grounded gap] + E --> F[Write design supplement and failing test] + F --> G[Implement one vertical slice] + G --> H[Validate in isolated copy without network] + H --> I{Queue and main unchanged?} + I -->|no| J[Discard stale proposal] + I -->|yes| K[Open exactly one PR] + K --> L[Central governance reviews and merges] ``` -The central ContextualWisdomLab workflows own review dispatch, review-feedback -repair, branch updates, required-check evaluation, auto-merge, and direct merge. -The product-development job cannot approve or merge its own work. +The repository workflow receives only read access to pull-request inventory. +After validation and repeated queue/base checks, it may exchange the existing +OIDC credential for a short-lived app token that pushes one generated branch +and opens one PR. It cannot approve, update, or merge that PR. ## Accuracy-first cadence @@ -43,8 +45,8 @@ queues later invocations rather than being killed at the next heartbeat. This is intentional: current-head correctness and reproducible evidence take precedence over wall-clock throughput. -The product job has a 180-minute budget. It starts only after all three queue -jobs succeed and no open pull request remains. +The product job has a 180-minute budget. It starts only when read-only live +inspection finds no open pull request; central governance continues independently. ## Product selection @@ -196,8 +198,9 @@ Generated PRs enter the same central loop as human-authored PRs: 5. an independent approval is required; 6. auto-merge or direct merge occurs without bypass. -Review wait time is not a blocker. Subsequent hourly invocations continue -repairing and revalidating the queue but do not create another product PR. +Review wait time is not a blocker. The central scheduler continues reviewing, +repairing, and revalidating the queue; the repository heartbeat exits without +creating another product PR while any pull request remains open. ## Failure behavior @@ -218,11 +221,10 @@ mutation step was reached. ## Operating evidence The permanent contract tests in -`tests/test_hourly_commercialization_workflow.py` verify the schedule, -governance pins, NVIDIA-only model path, credential removal, red/green -discipline, protected paths, isolated validation, stale-work checks, and -one-PR/no-self-merge boundary. - -The central scheduler remains independently active. This repository workflow -adds a LineageWeave-specific hourly heartbeat and product-gap generator; it -does not duplicate the central scheduler's implementation. +`tests/test_hourly_commercialization_workflow.py` verify the schedule, central +single-writer boundary, read-only live queue gate, NVIDIA-only model path, +credential removal, red/green discipline, protected paths, isolated +validation, stale-work checks, and one-PR/no-self-merge boundary. + +The central scheduler remains independently active every 15 minutes. This +repository workflow contributes only the LineageWeave product-gap heartbeat.