Skip to content

feat(runtime): add project-visible runtime roots#191

Merged
MacAttak merged 4 commits intomainfrom
work/02-project-visible-runtime-foundation
Apr 20, 2026
Merged

feat(runtime): add project-visible runtime roots#191
MacAttak merged 4 commits intomainfrom
work/02-project-visible-runtime-foundation

Conversation

@MacAttak
Copy link
Copy Markdown
Contributor

Summary

This unit adds the runtime-root foundation for the workflow legibility and operator-devex workstream. It introduces an explicit git.runtime.mode / git.runtime.projectVisibleRoot policy surface, teaches the shared resolver to support both git-admin and project-visible layouts, and proves the new mode fails closed on unsafe roots without weakening verify strictness or tracked work-artifact separation.

Because this repository uses clone-local work artifacts, the reviewer-usable audit summary is inlined below instead of depending on local-only .git/specwright/... paths.

Approval Lineage

  • Design approval: APPROVED from /sw-plan. The current design artifact set still matches the recorded approval hash.
  • Unit-spec approval for 02-project-visible-runtime-foundation: STALE (artifact-set-changed) from /sw-build. plan.md changed after approval when build appended the as-built notes.
  • Accepted-mutant lineage: none recorded in this verify run.

What Changed

  • .specwright/config.json now defines the runtime-root policy surface through git.runtime.mode and git.runtime.projectVisibleRoot while leaving git.workArtifacts as the separate publication-policy surface.
  • core/protocols/context.md now documents both runtime modes, the five logical roots, and the project-visible guardrails that keep clone-local runtime state out of .git and tracked artifacts.
  • adapters/shared/specwright-state-paths.mjs now resolves git-admin and project-visible runtime roots, keys linked worktrees by worktreeId, preserves tracked work-artifact overrides, and rejects unsafe project-visible roots.
  • .gitignore now excludes /.specwright-local/ so the project-visible runtime root stays untracked by default.
  • evals/tests/test_runtime_mode_paths.py now locks the runtime contract with real Git temp repos, linked-worktree coverage, tracked-root override coverage, and invalid-root rejection coverage.

Why The Agent Implemented It This Way

  • Task 1 defined the runtime-mode contract in tracked config and protocol text first so later resolver work had one explicit vocabulary to implement.
  • Task 2 centralized root derivation and tracked-root separation inside adapters/shared/specwright-state-paths.mjs so every existing hook and helper keeps consuming one shared resolver.
  • Task 3 finished the unit by proving the failure-closed path behavior and the repo ignore boundary instead of widening the runtime surface again.

Acceptance Criteria

  • AC-1 — PASS
    Implementation: .specwright/config.json:40-46; core/protocols/context.md:12-14,21-54
    Evidence: evals/tests/test_runtime_mode_paths.py:252-337 (TestRuntimeModeConfigDefaults, TestRuntimeModeContextProtocol)
  • AC-2 — PASS
    Implementation: adapters/shared/specwright-state-paths.mjs:246-370,409-465,652-689
    Evidence: evals/tests/test_runtime_mode_paths.py:343-419 (TestRuntimeModeResolverPaths::test_git_admin_primary_worktree_keeps_runtime_under_git_admin, ::test_project_visible_primary_worktree_moves_runtime_out_of_git, ::test_project_visible_linked_worktree_keys_state_by_worktree_id)
  • AC-3 — PASS
    Implementation: adapters/shared/specwright-state-paths.mjs:373-406,458-464,749-780
    Evidence: evals/tests/test_runtime_mode_paths.py:420-450 (TestRuntimeModeResolverPaths::test_tracked_work_artifacts_override_stays_independent_in_project_visible_mode)
  • AC-4 — PASS
    Implementation: adapters/shared/specwright-state-paths.mjs:1-4,180-209,259-333
    Evidence: evals/tests/test_runtime_mode_paths.py:459-503 (TestRuntimeModeSafetyProof::test_project_visible_root_inside_git_is_rejected, ::test_project_visible_root_inside_project_artifacts_is_rejected, ::test_project_visible_root_symlinked_into_git_is_rejected)
  • AC-5 — PASS
    Implementation: .gitignore:1-15; adapters/shared/specwright-state-paths.mjs:338-370,652-689
    Evidence: evals/tests/test_runtime_mode_paths.py:369-394,456-522 (TestRuntimeModeResolverPaths::test_project_visible_primary_worktree_moves_runtime_out_of_git, TestRuntimeModeSafetyProof::test_gitignore_excludes_project_visible_runtime_root_by_default, ::test_legacy_git_admin_install_still_loads_when_runtime_block_is_absent)

Spec Conformance

  • gate-spec: PASS. AC-1 through AC-5 passed in the canonical compliance matrix for this unit.
  • Behavioral IC mapping was inactive for this verify run because this is not the final work unit in the design.

Gate Summary

Gate Status Evidence-grounded summary
build PASS bash build/build.sh passed, and the configured commands.test tier passed with the full eval suite plus the Claude Code build regressions.
tests PASS The changed regression uses exact assertions, real Git temp repos, real Node execution, and a T3 mutation-resistance floor with all bypass classes passing.
security PASS No secrets, injection issues, or unsafe state-placement behavior were introduced; the resolver validates project-visible roots and keeps tracked artifacts separate.
wiring PASS The shared state-path resolver remains consumed by hooks, freshness helpers, and runtime-state regressions, so the unit does not leave a dead runtime path behind.
semantic PASS The changed helper logic stays fail-closed on invalid roots and unsafe tracked-root overlaps without introducing unchecked-error or fail-open regressions.
spec PASS The acceptance-criteria matrix maps each AC to implementation and test evidence with no WARN or BLOCK findings.

Remaining Attention

  • The current unit-spec approval lineage is stale because plan.md changed after /sw-build recorded approval.

Evidence Links

  • Implementation surfaces: .specwright/config.json, core/protocols/context.md, adapters/shared/specwright-state-paths.mjs, .gitignore
  • Regression proof: evals/tests/test_runtime_mode_paths.py
  • Clone-local verify artifacts were generated under local Specwright state and are summarized inline above for reviewer use.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

Eval Smoke Results

✅ Smoke evals clean — 0 regressions, 5 improvements

Eval Pass Rate Duration Tokens (input+output) Verdict
grader-function-tests 1.00 (+0.00) 667ms (-827ms) 0 (+0) improved
structural-handoff-template 1.00 (+0.00) 31ms (-12ms) 0 (+0) improved
structural-skill-validation 1.00 (+0.00) 1094ms (-1464ms) 0 (+0) improved
structural-state-enforcement 1.00 (+0.00) 76ms (-15ms) 0 (+0) improved
workflow-yaml-validation 1.00 (+0.00) 1134ms (-166ms) 0 (+0) improved

Posted by Specwright eval-smoke workflow. This comment is updated on each push.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec3ecbabe1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapters/shared/specwright-state-paths.mjs Outdated
Comment thread adapters/shared/specwright-state-paths.mjs
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 20, 2026

Review: feat(runtime) — add project-visible runtime roots

Overview

This PR introduces a two-mode runtime-root policy (git-admin / project-visible) so Specwright state can live in a repo-visible, gitignored directory rather than inside .git. The config surface, protocol documentation, resolver refactor, and regression suite are all present and structurally sound. The safety-proof tests (reject inside-.git, reject inside-artifacts, reject symlinked .git) are correct and cover the obvious bypass classes.

However, there is a functional divergence between the workArtifactsRoot the resolver reports and the directory that loadSpecwrightState / resolveSharedWorkflowPath actually use to locate workflow files. This will cause skills to read and write work artifacts in split locations under project-visible mode.


Critical — workArtifactsRoot / workflow-lookup split in project-visible mode

In git-admin mode both halves agree:

  • workArtifactsRoot = {repoStateRoot}/work = {gitCommonDir}/specwright/work
  • resolveSharedWorkflowPath(repoStateRoot, …){repoStateRoot}/work/{id}/workflow.json

In project-visible mode they diverge:

  • workArtifactsRoot = {sharedRuntimeRoot}/work ({parent}/.specwright-local/work)
  • resolveSharedWorkflowPath(repoStateRoot, …){sharedRuntimeRoot}/repo/work/{id}/workflow.json
  • loadSpecwrightState scans join(legacy.repoStateRoot, 'work') → same repo/work path

normalizeActiveWork (line 749) then computes workDirPath and artifactsRoot from workArtifactsRoot = {sharedRuntimeRoot}/work, not from {sharedRuntimeRoot}/repo/work. Any skill that creates a work dir under workArtifactsRoot won't be found by loadSpecwrightState, and any workDirPath rendered to the agent points to the wrong subtree.

The test test_project_visible_primary_worktree_moves_runtime_out_of_git accepts the split by asserting workflowPath = visible_root/repo/work/… alongside workArtifactsRoot = visible_root/work without checking artifactsRoot or workDirPath — see inline note on that test.

Fix options:

  • A (low risk): change cloneLocalWorkArtifactsRoot for project-visible to join(sharedRuntimeRoot, 'repo', 'work') (i.e. join(repoStateRoot, 'work')). Keeps the existing repoStateRoot/work convention, zero changes to loadSpecwrightState / resolveSharedWorkflowPath.
  • B (protocol-aligned): update resolveSharedWorkflowPath to accept the pre-resolved workArtifactsRoot and thread it through loadSpecwrightState. Matches the protocol definition but has wider blast radius.

Notable findings

Missing negative-path tests in TestRuntimeModeSafetyProof

The class covers inside-.git, inside-.specwright, and symlinked-.git. It does not test:

  • projectVisibleRoot = "../outside" (path traversal) — the isPathWithin check blocks this, but mutation resistance is low without an explicit test.
  • projectVisibleRoot = "/tmp/absolute" (absolute path) — resolve(runtimeParent, '/tmp/absolute') returns the absolute path, which then fails isPathWithin, but again no test.

_derive_worktree_id fallback divergence

The Python helper raises AssertionError when it can't derive a worktree id from directory structure. The JS deriveWorktreeId falls back to worktree-{sha256[:12]}. The hash branch is untested and the Python helper would throw before ever reaching it.

Double config read in resolveWorkArtifactsRoot

resolveRuntimePolicy already called loadResolvedConfig; resolveWorkArtifactsRoot calls it again. In project-visible mode the fallback path differs ({sharedRuntimeRoot}/repo/config.json vs {gitCommonDir}/specwright/config.json), so the two reads could theoretically return different configs. No practical impact today but worth documenting.

listSessionFiles repeated mode branches

Three separate roots.runtimeMode === 'project-visible' ternaries could be collapsed into two extracted variables (primarySessionPath, linkedWorktreesDir) before the loop.


Stale unit-spec approval

The PR itself flags artifact-set-changed on the unit-spec approval. This is an intra-unit concern but should be re-run before merge to ensure the approval hash matches the final plan.


Gate verdicts

Build, tests, security, wiring, semantic, and spec gates are all asserted PASS and the evidence links are present. The test file uses real Git temp repos and Node execution, which is the right approach. The AC matrix is complete. The single concern is the test gap on clone-local project-visible workDirPath / artifactsRoot noted above.

Comment thread adapters/shared/specwright-state-paths.mjs Outdated
Comment thread evals/tests/test_runtime_mode_paths.py
Comment thread evals/tests/test_runtime_mode_paths.py
Comment thread evals/tests/test_runtime_mode_paths.py Outdated
@MacAttak MacAttak merged commit 6bc65d5 into main Apr 20, 2026
25 checks passed
@MacAttak MacAttak deleted the work/02-project-visible-runtime-foundation branch April 20, 2026 07:43
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2460169aaa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +124 to +125
if (linkedWorktreeId && linkedWorktreeId !== PRIMARY_WORKTREE_ID) {
return linkedWorktreeId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep linked main-worktree ID stable in git-admin mode

When a linked worktree is literally named main-worktree (which Git permits), this new guard skips that directory name and forces a hashed worktreeId. In default git-admin mode, session discovery still keys that same worktree as main-worktree from .git/worktrees/<name>, so findSelectedWorkOwnerConflict compares two different IDs for the same checkout and reports a false “owned by another worktree” conflict. This produces incorrect adopt/takeover warnings in session-start for a single active worktree.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot mentioned this pull request Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant