feat(plan-01): Phase 4c — hard cutoff, remove .specify compat facade

Repoint every remaining literal `.specify/...` / `docs/input` reference to the real
package/domain location and delete all compat symlinks. The harness now runs purely via
packages/casan-harness/... with no .specify facade; .specify holds ONLY runtime state
(logs/, agentops/alerts.log, level5/central-governance).

Refs fixed (Phase 0.5 only caught `$VAR/.specify/` — these were bare/`__file__`/literal):
- secrets-scan.sh: scan-target excludes -> packages/casan-harness/... (+ apps/okr/domain/corpus)
- loop_common.py: loop-policy.yaml -> harness config (package-relative)
- evidence-pack-build.py: judge-gate test + traceability-matrix.py -> harness/sibling
- phase10-traceability: REQ -> $CASAN_DOMAIN_ROOT/input
- run-casan-pipeline.mjs: model-fallback/drift-detect/rollback-manager -> HARNESS_BASH,
  golden -> GOLDEN_PLAN (apps/okr/domain), with .specify/logs state kept
- casan-step.mjs: requirement fallback restored to docs/input for hermetic sandboxes
- descriptive config (tool-registry/harness-package/drift-policy/hallucination/risk-registry/
  loop-policy.schema + docstrings) repointed for accuracy
- policy-bundle.yaml: 8 policy paths -> packages/casan-harness/...; manifest regenerated +
  re-signed (POLICY_HASHES_VALID files=8, POLICY_SIGNATURE_VALID)

Removed 22 .specify code/config symlinks + docs/input symlink.
Full gate via packages path, NO facade: PASS=64 FAIL=0 SKIP=3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
thanhnv
2026-07-08 12:28:37 +09:00
co-authored by Claude Opus 4.8
parent e891981b59
commit 7101af9fd4
38 changed files with 48 additions and 60 deletions
@@ -1 +0,0 @@
../../packages/casan-harness/agentops/alerts.yaml
@@ -1 +0,0 @@
../../packages/casan-harness/agentops/hallucination-tracking.yaml
@@ -1 +0,0 @@
../../packages/casan-harness/agentops/metrics.yaml
@@ -1 +0,0 @@
../../packages/casan-harness/agentops/rate-limits.yaml
@@ -1 +0,0 @@
../../packages/casan-harness/agentops/tracking.yaml
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/config
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/governance
@@ -1 +0,0 @@
../packages/casan-harness/init-options.json
@@ -1 +0,0 @@
../../packages/casan-harness/level5/drift-policy.yaml
@@ -1 +0,0 @@
../../apps/okr/domain/golden-runs
@@ -1 +0,0 @@
../../packages/casan-harness/level5/harness-package.json
@@ -1 +0,0 @@
../../packages/casan-harness/level5/kpi-schema.yaml
@@ -1 +0,0 @@
../../packages/casan-harness/level5/model-fallback.yaml
@@ -1 +0,0 @@
../../packages/casan-harness/level5/project-registry.json
@@ -1 +0,0 @@
../../packages/casan-harness/level5/provider-usage-sample.json
@@ -1 +0,0 @@
../../packages/casan-harness/level5/tool-registry.yaml
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/memory
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/scripts
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/security
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/templates
-1
View File
@@ -1 +0,0 @@
../packages/casan-harness/tests
@@ -1 +0,0 @@
../apps/okr/domain/traceability-map.json
-1
View File
@@ -1 +0,0 @@
../apps/okr/domain/input
@@ -10,7 +10,7 @@ tracking:
steps_with_tracking: steps_with_tracking:
- step: step-1-srs - step: step-1-srs
reference_docs: reference_docs:
- docs/input/okr-requirement.md - apps/okr/domain/input/okr-requirement.md
signals: signals:
- ungrounded_feature_claim - ungrounded_feature_claim
- invented_requirement - invented_requirement
@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://casan.fpt/loop-policy.schema.json", "$id": "https://casan.fpt/loop-policy.schema.json",
"title": "CASAN Loop Budget Governor policy", "title": "CASAN Loop Budget Governor policy",
"description": "Schema for .specify/config/loop-policy.yaml (Plan-17 Track 1). Budgets are non-negative numbers; on_exceed is halt|escalate. Absence of a rule means the strictest built-in ceiling applies (deny-by-default).", "description": "Schema for packages/casan-harness/config/loop-policy.yaml (Plan-17 Track 1). Budgets are non-negative numbers; on_exceed is halt|escalate. Absence of a rule means the strictest built-in ceiling applies (deny-by-default).",
"type": "object", "type": "object",
"required": ["version", "profiles"], "required": ["version", "profiles"],
"additionalProperties": false, "additionalProperties": false,
@@ -83,7 +83,7 @@ reporting:
enabled: true enabled: true
frequency: daily frequency: daily
output: ".specify/governance/risk-report.md" output: "packages/casan-harness/governance/risk-report.md"
review: review:
@@ -93,6 +93,6 @@ review:
auto_update: auto_update:
enabled: true enabled: true
trigger: new_action_type_detected trigger: new_action_type_detected
script: .specify/scripts/powershell/update-risk-registry.ps1 script: packages/casan-harness/scripts/powershell/update-risk-registry.ps1
default_risk_for_unknown_action: high default_risk_for_unknown_action: high
note: "Unknown actions default to high-risk (fail-secure). Run update-risk-registry.ps1 to register." note: "Unknown actions default to high-risk (fail-secure). Run update-risk-registry.ps1 to register."
@@ -1,7 +1,7 @@
version: 1.0 version: 1.0
description: CASAN Level 5 drift detection policy description: CASAN Level 5 drift detection policy
golden_dir: .specify/level5/golden-runs golden_dir: apps/okr/domain/golden-runs
evidence_dir: docs/output/casan/level5-evidence evidence_dir: docs/output/casan/level5-evidence
thresholds: thresholds:
@@ -17,8 +17,8 @@
"python": ">=3.9" "python": ">=3.9"
}, },
"entrypoints": { "entrypoints": {
"wrapper": ".specify/scripts/bash/casan-harness.sh", "wrapper": "packages/casan-harness/scripts/bash/casan-harness.sh",
"test": ".specify/tests/run-casan4-harness-tests.sh", "test": "packages/casan-harness/tests/run-casan4-harness-tests.sh",
"dashboard": "docs/output/casan/agentops-dashboard.html" "dashboard": "docs/output/casan/agentops-dashboard.html"
} }
} }
@@ -18,7 +18,7 @@ tools:
allowed_agents: "boss,implement-agent" allowed_agents: "boss,implement-agent"
rollback: rollback:
strategy: git_patch_reverse strategy: git_patch_reverse
script: .specify/scripts/bash/rollback-manager.sh script: packages/casan-harness/scripts/bash/rollback-manager.sh
- id: migration - id: migration
owner: database-owner owner: database-owner
@@ -29,7 +29,7 @@ tools:
allowed_agents: "boss,implement-agent" allowed_agents: "boss,implement-agent"
rollback: rollback:
strategy: migration_down_or_restore strategy: migration_down_or_restore
script: .specify/scripts/bash/rollback-manager.sh script: packages/casan-harness/scripts/bash/rollback-manager.sh
- id: deploy - id: deploy
owner: release-manager owner: release-manager
@@ -41,7 +41,7 @@ tools:
allowed_agents: "boss,release-manager" allowed_agents: "boss,release-manager"
rollback: rollback:
strategy: previous_release strategy: previous_release
script: .specify/scripts/bash/rollback-manager.sh script: packages/casan-harness/scripts/bash/rollback-manager.sh
- id: external_api - id: external_api
owner: platform-team owner: platform-team
@@ -52,7 +52,7 @@ tools:
allowed_agents: "boss,implement-agent" allowed_agents: "boss,implement-agent"
rollback: rollback:
strategy: compensating_request strategy: compensating_request
script: .specify/scripts/bash/rollback-manager.sh script: packages/casan-harness/scripts/bash/rollback-manager.sh
- id: db_write - id: db_write
owner: database-owner owner: database-owner
@@ -63,7 +63,7 @@ tools:
allowed_agents: "boss,implement-agent" allowed_agents: "boss,implement-agent"
rollback: rollback:
strategy: restore_from_backup strategy: restore_from_backup
script: .specify/scripts/powershell/rollback-manager.ps1 script: packages/casan-harness/scripts/powershell/rollback-manager.ps1
- id: write_file - id: write_file
owner: engineering owner: engineering
@@ -74,7 +74,7 @@ tools:
allowed_agents: "boss,implement-agent,design-agent" allowed_agents: "boss,implement-agent,design-agent"
rollback: rollback:
strategy: delete_or_restore strategy: delete_or_restore
script: .specify/scripts/bash/rollback-manager.sh script: packages/casan-harness/scripts/bash/rollback-manager.sh
level5_gate: level5_gate:
required_fields: required_fields:
@@ -21,6 +21,9 @@ import os
import subprocess import subprocess
import sys import sys
# Plan-01: this script lives at <harness>/scripts/bash/; the harness root is two levels up.
_HARNESS = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
def read_jsonl(path): def read_jsonl(path):
rows = [] rows = []
@@ -73,11 +76,11 @@ def main():
# H3 eval scorecard (best effort — reference known evidence) # H3 eval scorecard (best effort — reference known evidence)
reports["h3-eval-scorecard.json"] = { reports["h3-eval-scorecard.json"] = {
"harness": "H3-eval", "run_id": run_id, "harness": "H3-eval", "run_id": run_id,
"judge_gate_tests": os.path.exists(os.path.join(root, ".specify/tests/phase3-judge-gate-tests.sh")), "judge_gate_tests": os.path.exists(os.path.join(_HARNESS, "tests/phase3-judge-gate-tests.sh")),
"note": "judge-gate fail-before/fix cycle proven by phase3-judge-gate-tests.sh", "note": "judge-gate fail-before/fix cycle proven by phase3-judge-gate-tests.sh",
} }
traceability_out = os.path.join(pack_dir, "traceability-matrix.json") traceability_out = os.path.join(pack_dir, "traceability-matrix.json")
traceability_script = os.path.join(root, ".specify/scripts/bash/traceability-matrix.py") traceability_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "traceability-matrix.py")
traceability_rc = 1 traceability_rc = 1
if os.path.isfile(traceability_script): if os.path.isfile(traceability_script):
traceability_rc = subprocess.run( traceability_rc = subprocess.run(
@@ -136,7 +136,9 @@ def policy_path() -> str:
explicit = os.environ.get("CASAN_LOOP_POLICY_FILE") explicit = os.environ.get("CASAN_LOOP_POLICY_FILE")
if explicit: if explicit:
return explicit return explicit
return os.path.join(project_root(), ".specify/config/loop-policy.yaml") # Plan-01: loop-policy.yaml is harness config, not app state — resolve it inside the
# package (this file lives at <harness>/scripts/bash/), independent of any .specify facade.
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "config", "loop-policy.yaml"))
def load_policy(): def load_policy():
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""CASAN H4 PII masker driven by .specify/security/pii-rules.yaml. """CASAN H4 PII masker driven by packages/casan-harness/security/pii-rules.yaml.
Reads content on stdin, applies every `action: mask` rule from the rules Reads content on stdin, applies every `action: mask` rule from the rules
file, and writes the masked content to stdout. Type-specific replacement file, and writes the masked content to stdout. Type-specific replacement
@@ -39,14 +39,15 @@ fi
# Exclude known test-fixture files and evidence directories that intentionally # Exclude known test-fixture files and evidence directories that intentionally
# contain the pattern as test data. # contain the pattern as test data.
FIXTURE_EXCLUDES=( FIXTURE_EXCLUDES=(
".specify/tests/" "packages/casan-harness/tests/"
"docs/output/casan/evidence/" "docs/output/casan/evidence/"
".specify/security/" "packages/casan-harness/security/"
".specify/scripts/bash/security-check.sh" "apps/okr/domain/corpus/"
".specify/scripts/bash/verify-audit-chain.sh" "packages/casan-harness/scripts/bash/security-check.sh"
".specify/scripts/bash/verify-tool-audit.sh" "packages/casan-harness/scripts/bash/verify-audit-chain.sh"
".specify/scripts/bash/tool-audit-lib.sh" "packages/casan-harness/scripts/bash/verify-tool-audit.sh"
".specify/scripts/bash/governance-check.sh" "packages/casan-harness/scripts/bash/tool-audit-lib.sh"
"packages/casan-harness/scripts/bash/governance-check.sh"
) )
build_exclude_args() { build_exclude_args() {
for ex in "${FIXTURE_EXCLUDES[@]}"; do printf -- "--exclude-dir=%s " "$ex"; done for ex in "${FIXTURE_EXCLUDES[@]}"; do printf -- "--exclude-dir=%s " "$ex"; done
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""CASAN Plan-10 traceability matrix generator/gate. """CASAN Plan-10 traceability matrix generator/gate.
Parses FR-* requirements from docs/input/okr-requirement.md and checks each Parses FR-* requirements from apps/okr/domain/input/okr-requirement.md and checks each
requirement has at least one existing code file and one existing test file. requirement has at least one existing code file and one existing test file.
""" """
import argparse import argparse
@@ -17,7 +17,7 @@ PASS=0; FAIL=0
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); } pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); } fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }
REQ="$PROJECT_ROOT/docs/input/okr-requirement.md" REQ="$CASAN_DOMAIN_ROOT/input/okr-requirement.md"
MAP="$CASAN_DOMAIN_ROOT/traceability-map.json" MAP="$CASAN_DOMAIN_ROOT/traceability-map.json"
OUT="$WORK/traceability-matrix.json" OUT="$WORK/traceability-matrix.json"
+1 -1
View File
@@ -1,5 +1,5 @@
// CASAN shared log helper (Node side). // CASAN shared log helper (Node side).
// Taxonomy (shared with .specify/scripts/bash/casan-log.sh): // Taxonomy (shared with packages/casan-harness/scripts/bash/casan-log.sh):
// error(0) < warn(1) < info(2) < debug(3) < trace(4), default info. // error(0) < warn(1) < info(2) < debug(3) < trace(4), default info.
// All lines go to stderr so stdout stays reserved for existing outputs. // All lines go to stderr so stdout stays reserved for existing outputs.
+1 -1
View File
@@ -252,7 +252,7 @@ switch (step) {
extraInstructions: 'Create an SRS with purpose, scope, functional requirements, non-functional requirements, and traceable FR IDs.', extraInstructions: 'Create an SRS with purpose, scope, functional requirements, non-functional requirements, and traceable FR IDs.',
}); });
write(srsPath, generated.content); write(srsPath, generated.content);
report(`docs/output/output_logs/${featureId}/reports/01-srs-report.md`, '# STEP 1: SRS Generation Report', `Generated ${srsPath} from docs/input/okr-requirement.md. source=${generated.source} note=${generated.note}.`, 'APPROVED', [srsPath]); report(`docs/output/output_logs/${featureId}/reports/01-srs-report.md`, '# STEP 1: SRS Generation Report', `Generated ${srsPath} from apps/okr/domain/input/okr-requirement.md. source=${generated.source} note=${generated.note}.`, 'APPROVED', [srsPath]);
break; break;
} }
case '02-bd': { case '02-bd': {
@@ -10,10 +10,15 @@ const dryRun = process.argv.includes('--dry-run');
const featureId = '001-okr-web-app'; const featureId = '001-okr-web-app';
const root = process.cwd(); const root = process.cwd();
// Plan-01: harness relocated to packages/casan-harness/; fall back to the .specify facade. // Plan-01: harness relocated to packages/casan-harness/, domain to apps/okr/domain/;
const HARNESS = existsSync(join(root, 'packages/casan-harness/scripts/bash/casan-harness.sh')) // fall back to the pre-move paths so this runner works from either layout.
? 'packages/casan-harness/scripts/bash/casan-harness.sh' const HARNESS_BASH = existsSync(join(root, 'packages/casan-harness/scripts/bash'))
: '.specify/scripts/bash/casan-harness.sh'; ? 'packages/casan-harness/scripts/bash'
: '.specify/scripts/bash';
const HARNESS = `${HARNESS_BASH}/casan-harness.sh`;
const GOLDEN_PLAN = existsSync(join(root, 'apps/okr/domain/golden-runs/okr-plan.golden.txt'))
? 'apps/okr/domain/golden-runs/okr-plan.golden.txt'
: '.specify/level5/golden-runs/okr-plan.golden.txt';
const logDir = `docs/output/output_logs/${featureId}`; const logDir = `docs/output/output_logs/${featureId}`;
const casanDir = `${logDir}/casan`; const casanDir = `${logDir}/casan`;
const reportsDir = `${logDir}/reports`; const reportsDir = `${logDir}/reports`;
@@ -340,7 +345,7 @@ runHarness({ id: '07-plan-attempt-2', agent: 'speckit.plan', step: '05-plan', at
const fallbackOut = `${casanDir}/model-fallback-output.txt`; const fallbackOut = `${casanDir}/model-fallback-output.txt`;
log('debug', 'boss', `model-fallback invoked (real primary failure) → ${fallbackOut}`); log('debug', 'boss', `model-fallback invoked (real primary failure) → ${fallbackOut}`);
execFileSync( execFileSync(
'.specify/scripts/bash/model-fallback.sh', `/model-fallback.sh`,
[ [
fallbackOut, fallbackOut,
// Real primary failure: reading a nonexistent path exits non-zero (not a // Real primary failure: reading a nonexistent path exits non-zero (not a
@@ -361,8 +366,8 @@ appendBoss(`Model fallback invoked; output ${fallbackOut}`);
const driftCandidate = `${casanDir}/drift-plan-candidate.txt`; const driftCandidate = `${casanDir}/drift-plan-candidate.txt`;
copyFileSync(fallbackOut, driftCandidate); copyFileSync(fallbackOut, driftCandidate);
log('debug', 'boss', 'drift-detect: fallback output vs golden baseline'); log('debug', 'boss', 'drift-detect: fallback output vs golden baseline');
execFileSync('.specify/scripts/bash/drift-detect.sh', [ execFileSync(`/drift-detect.sh`, [
'.specify/level5/golden-runs/okr-plan.golden.txt', GOLDEN_PLAN,
driftCandidate, driftCandidate,
'.specify/logs/level5/okr-plan-drift-report.json', '.specify/logs/level5/okr-plan-drift-report.json',
], { cwd: root, stdio: 'inherit' }); ], { cwd: root, stdio: 'inherit' });
@@ -390,7 +395,7 @@ copyFileSync(rollbackTarget, rollbackBackup);
writeFileSync(`${rollbackDir}/rollback-before.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8'); writeFileSync(`${rollbackDir}/rollback-before.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8');
writeFileSync(rollbackTarget, 'changed content that must be undone\n', 'utf8'); writeFileSync(rollbackTarget, 'changed content that must be undone\n', 'utf8');
writeFileSync(`${rollbackDir}/rollback-changed.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8'); writeFileSync(`${rollbackDir}/rollback-changed.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8');
const record = execFileSync('.specify/scripts/bash/rollback-manager.sh', [ const record = execFileSync(`/rollback-manager.sh`, [
'record', 'record',
'restore rollback target evidence file', 'restore rollback target evidence file',
`cp ${rollbackBackup} ${rollbackTarget}`, `cp ${rollbackBackup} ${rollbackTarget}`,
@@ -400,7 +405,7 @@ const tx = record.match(/transaction_id=([^\s]+)/)?.[1];
if (!tx) { if (!tx) {
throw new Error('rollback transaction id not found'); throw new Error('rollback transaction id not found');
} }
const execute = execFileSync('.specify/scripts/bash/rollback-manager.sh', ['execute', tx], { const execute = execFileSync(`/rollback-manager.sh`, ['execute', tx], {
cwd: root, cwd: root,
encoding: 'utf8', encoding: 'utf8',
}); });