feat(plan-01): Phase 4b — external callers to packages paths + registry (Task 1.7)
Point the canonical entry points at the new package layout; the .specify compat facade
is retained as an intentional backward-compat layer (see below).
- .gitea/workflows/{ci,harness-ci}.yml: invoke packages/casan-harness/scripts/bash|tests
(state paths .specify/logs kept). CI now runs on the new structure.
- infra/local-prod/docker-compose.yml: dashboard-server.py -> packages path (logs/alerts
env stay under .specify state).
- scripts/casan-step.mjs + run-casan-pipeline.mjs: resolve the harness under
packages/casan-harness, falling back to .specify so the adversarial/sourcegen sandboxes
(which stage a .specify/ tree) keep working; requirement input prefers apps/okr/domain/input.
- project-registry.json: record the new layout (harness_root, state_root, governance_root,
per-project domain_root) so Plan-06 can register a second app with its own domain.
verify-harness-reuse.sh already resolves via CASAN_HARNESS_ROOT -> HARNESS_REUSE_VALID (3 projects).
Facade decision: the .specify/{scripts,tests,security,...} symlinks are KEPT as a
documented compat layer. A full hard cutover (removing them) still needs ~15 literal
`.specify/...` refs repointed (loop_common/evidence-pack/secrets-scan config+test paths,
run-casan-pipeline step scripts, and the signed policy-bundle.yaml path list which then
needs manifest regen + re-sign). That is a scoped follow-up; the physical separation
(code in packages/, domain in apps/, packages holds no domain data, single CASAN_* path
indirection) is complete and the full gate is green via BOTH entry paths: PASS=64 FAIL=0 SKIP=3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3adc48ed82
commit
e891981b59
@@ -151,15 +151,15 @@ export VAULT_TOKEN=<TOKEN>
|
|||||||
cd Output_CASAN5_REFINED/AINative_OKR_CASAN5
|
cd Output_CASAN5_REFINED/AINative_OKR_CASAN5
|
||||||
|
|
||||||
# Test sign-policy-bundle with Vault
|
# Test sign-policy-bundle with Vault
|
||||||
bash .specify/scripts/bash/sign-policy-bundle.sh sign
|
bash packages/casan-harness/scripts/bash/sign-policy-bundle.sh sign
|
||||||
# Expected: POLICY_BUNDLE_SIGNED ... key_backend=vault-kms
|
# Expected: POLICY_BUNDLE_SIGNED ... key_backend=vault-kms
|
||||||
|
|
||||||
# Test sign-audit-head
|
# Test sign-audit-head
|
||||||
bash .specify/scripts/bash/sign-audit-head.sh
|
bash packages/casan-harness/scripts/bash/sign-audit-head.sh
|
||||||
# Expected: SIGN_AUDIT_HEAD_OK ... anchor=vault-kms
|
# Expected: SIGN_AUDIT_HEAD_OK ... anchor=vault-kms
|
||||||
|
|
||||||
# Verify audit chain
|
# Verify audit chain
|
||||||
bash .specify/scripts/bash/verify-audit-chain.sh
|
bash packages/casan-harness/scripts/bash/verify-audit-chain.sh
|
||||||
# Expected: AUDIT_CHAIN_VALID anchor=signed
|
# Expected: AUDIT_CHAIN_VALID anchor=signed
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -168,9 +168,9 @@ bash .specify/scripts/bash/verify-audit-chain.sh
|
|||||||
## Step 7 — Push code to trigger CI with Vault
|
## Step 7 — Push code to trigger CI with Vault
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add .gitea/ .specify/scripts/bash/vault-kms.sh \
|
git add .gitea/ packages/casan-harness/scripts/bash/vault-kms.sh \
|
||||||
.specify/scripts/bash/sign-audit-head.sh \
|
packages/casan-harness/scripts/bash/sign-audit-head.sh \
|
||||||
.specify/scripts/bash/sign-policy-bundle.sh
|
packages/casan-harness/scripts/bash/sign-policy-bundle.sh
|
||||||
git commit -m "feat(H5): Vault KMS for policy + audit chain signing"
|
git commit -m "feat(H5): Vault KMS for policy + audit chain signing"
|
||||||
git push gitea main
|
git push gitea main
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -83,9 +83,9 @@ jobs:
|
|||||||
# Non-blocking: if Vault is unreachable, scripts fall back gracefully.
|
# Non-blocking: if Vault is unreachable, scripts fall back gracefully.
|
||||||
run: |
|
run: |
|
||||||
if curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then
|
if curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then
|
||||||
bash .specify/scripts/bash/vault-kms.sh enable-transit
|
bash packages/casan-harness/scripts/bash/vault-kms.sh enable-transit
|
||||||
bash .specify/scripts/bash/vault-kms.sh ensure-key casan-policy-key
|
bash packages/casan-harness/scripts/bash/vault-kms.sh ensure-key casan-policy-key
|
||||||
bash .specify/scripts/bash/vault-kms.sh ensure-key casan-audit-key
|
bash packages/casan-harness/scripts/bash/vault-kms.sh ensure-key casan-audit-key
|
||||||
echo "VAULT_KMS_READY"
|
echo "VAULT_KMS_READY"
|
||||||
else
|
else
|
||||||
echo "VAULT_KMS_SKIP (unreachable — will use local-file fallback)"
|
echo "VAULT_KMS_SKIP (unreachable — will use local-file fallback)"
|
||||||
@@ -93,27 +93,27 @@ jobs:
|
|||||||
|
|
||||||
- name: Run CASAN4 harness tests (35 tests)
|
- name: Run CASAN4 harness tests (35 tests)
|
||||||
# This clears .specify/logs/ and rebuilds audit.jsonl from scratch.
|
# This clears .specify/logs/ and rebuilds audit.jsonl from scratch.
|
||||||
run: bash .specify/tests/run-casan4-harness-tests.sh
|
run: bash packages/casan-harness/tests/run-casan4-harness-tests.sh
|
||||||
|
|
||||||
- name: Run adversarial harness tests (44 tests)
|
- name: Run adversarial harness tests (44 tests)
|
||||||
run: bash .specify/tests/adversarial-harness-tests.sh
|
run: bash packages/casan-harness/tests/adversarial-harness-tests.sh
|
||||||
|
|
||||||
- name: Sign audit chain head via Vault KMS (H5)
|
- name: Sign audit chain head via Vault KMS (H5)
|
||||||
# After harness tests rebuild audit.jsonl, sign the head so that
|
# After harness tests rebuild audit.jsonl, sign the head so that
|
||||||
# verify-audit-chain.sh reports "anchor=signed" (not "anchor=unsigned").
|
# verify-audit-chain.sh reports "anchor=signed" (not "anchor=unsigned").
|
||||||
run: bash .specify/scripts/bash/sign-audit-head.sh
|
run: bash packages/casan-harness/scripts/bash/sign-audit-head.sh
|
||||||
|
|
||||||
- name: Sign policy bundle via Vault KMS (H5)
|
- name: Sign policy bundle via Vault KMS (H5)
|
||||||
run: bash .specify/scripts/bash/sign-policy-bundle.sh sign
|
run: bash packages/casan-harness/scripts/bash/sign-policy-bundle.sh sign
|
||||||
|
|
||||||
- name: Verify audit chain (anchor=signed expected in CI)
|
- name: Verify audit chain (anchor=signed expected in CI)
|
||||||
run: bash .specify/scripts/bash/verify-audit-chain.sh
|
run: bash packages/casan-harness/scripts/bash/verify-audit-chain.sh
|
||||||
|
|
||||||
- name: Security gate — aggregate verdict (PASS=7 SKIP=1 FAIL=0)
|
- name: Security gate — aggregate verdict (PASS=7 SKIP=1 FAIL=0)
|
||||||
# Ollama-dependent checks auto-SKIP (not FAIL) when Ollama is not reachable.
|
# Ollama-dependent checks auto-SKIP (not FAIL) when Ollama is not reachable.
|
||||||
# security-gate.sh re-runs harness + adversarial + verify + scan + frontend.
|
# security-gate.sh re-runs harness + adversarial + verify + scan + frontend.
|
||||||
# Exit 0 only when FAIL=0.
|
# Exit 0 only when FAIL=0.
|
||||||
run: bash .specify/scripts/bash/security-gate.sh
|
run: bash packages/casan-harness/scripts/bash/security-gate.sh
|
||||||
|
|
||||||
- name: Upload test evidence
|
- name: Upload test evidence
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -34,4 +34,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Run CASAN harness gate
|
- name: Run CASAN harness gate
|
||||||
working-directory: AINative_OKR_CASAN5
|
working-directory: AINative_OKR_CASAN5
|
||||||
run: bash .specify/scripts/bash/ci-harness-gate.sh
|
run: bash packages/casan-harness/scripts/bash/ci-harness-gate.sh
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ services:
|
|||||||
dashboard:
|
dashboard:
|
||||||
image: python:3.12-slim
|
image: python:3.12-slim
|
||||||
working_dir: /workspace
|
working_dir: /workspace
|
||||||
command: ["python", ".specify/scripts/bash/dashboard-server.py", "8787"]
|
command: ["python", "packages/casan-harness/scripts/bash/dashboard-server.py", "8787"]
|
||||||
environment:
|
environment:
|
||||||
CASAN_DASHBOARD_BIND: 0.0.0.0
|
CASAN_DASHBOARD_BIND: 0.0.0.0
|
||||||
CASAN_DASHBOARD_STALE_S: "315360000"
|
CASAN_DASHBOARD_STALE_S: "315360000"
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
{
|
{
|
||||||
"registry": "fpt-casan-harness-adoption",
|
"registry": "fpt-casan-harness-adoption",
|
||||||
|
"layout": {
|
||||||
|
"note": "Plan-01 restructure: reusable harness is a standalone package; each project keeps its domain data separate. A new project (Plan-06) registers here with its own domain_root.",
|
||||||
|
"harness_root": "packages/casan-harness",
|
||||||
|
"state_root": ".specify",
|
||||||
|
"governance_root": ".specify/level5/central-governance"
|
||||||
|
},
|
||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"project_id": "AINative_OKR_CASAN4",
|
"project_id": "AINative_OKR_CASAN4",
|
||||||
"domain": "SDD OKR",
|
"domain": "SDD OKR",
|
||||||
|
"domain_root": "apps/okr/domain",
|
||||||
"harness_package": "fpt-casan-sdd-harness",
|
"harness_package": "fpt-casan-sdd-harness",
|
||||||
"harness_version": "1.0.0",
|
"harness_version": "1.0.0",
|
||||||
"status": "active"
|
"status": "active"
|
||||||
@@ -11,6 +18,7 @@
|
|||||||
{
|
{
|
||||||
"project_id": "CASAN_DEMO_PROJECT_A",
|
"project_id": "CASAN_DEMO_PROJECT_A",
|
||||||
"domain": "Demo SDLC",
|
"domain": "Demo SDLC",
|
||||||
|
"domain_root": "apps/demo-sdlc/domain",
|
||||||
"harness_package": "fpt-casan-sdd-harness",
|
"harness_package": "fpt-casan-sdd-harness",
|
||||||
"harness_version": "1.0.0",
|
"harness_version": "1.0.0",
|
||||||
"status": "registered"
|
"status": "registered"
|
||||||
@@ -18,6 +26,7 @@
|
|||||||
{
|
{
|
||||||
"project_id": "CASAN_DEMO_PROJECT_B",
|
"project_id": "CASAN_DEMO_PROJECT_B",
|
||||||
"domain": "Demo QA",
|
"domain": "Demo QA",
|
||||||
|
"domain_root": "apps/demo-qa/domain",
|
||||||
"harness_package": "fpt-casan-sdd-harness",
|
"harness_package": "fpt-casan-sdd-harness",
|
||||||
"harness_version": "1.0.0",
|
"harness_version": "1.0.0",
|
||||||
"status": "registered"
|
"status": "registered"
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
// WP-B (H3): model judge gate helpers — AND(rule, model); SKIP is non-blocking.
|
// WP-B (H3): model judge gate helpers — AND(rule, model); SKIP is non-blocking.
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const SCRIPTS_DIR = join(dirname(__filename), '..', '.specify', 'scripts', 'bash');
|
// Plan-01: harness code lives in packages/casan-harness/; fall back to the .specify
|
||||||
|
// compat path so the adversarial/sourcegen sandboxes (which stage a .specify/ tree)
|
||||||
|
// keep working when this file is copied into a temp root.
|
||||||
|
const __appRoot = join(dirname(__filename), '..');
|
||||||
|
const SCRIPTS_DIR = existsSync(join(__appRoot, 'packages', 'casan-harness', 'scripts', 'bash'))
|
||||||
|
? join(__appRoot, 'packages', 'casan-harness', 'scripts', 'bash')
|
||||||
|
: join(__appRoot, '.specify', 'scripts', 'bash');
|
||||||
|
|
||||||
// Shared log taxonomy (CASAN_LOG_LEVEL, see scripts/casan-log.mjs). The
|
// Shared log taxonomy (CASAN_LOG_LEVEL, see scripts/casan-log.mjs). The
|
||||||
// adversarial T1 test copies this file alone into a temp tree, so the logger
|
// adversarial T1 test copies this file alone into a temp tree, so the logger
|
||||||
@@ -180,7 +186,10 @@ const dirs = [
|
|||||||
];
|
];
|
||||||
dirs.forEach((dir) => mkdirSync(dir, { recursive: true }));
|
dirs.forEach((dir) => mkdirSync(dir, { recursive: true }));
|
||||||
|
|
||||||
const requirement = readFileSync('docs/input/okr-requirement.md', 'utf8');
|
const requirement = readFileSync(
|
||||||
|
existsSync('apps/okr/domain/input/okr-requirement.md')
|
||||||
|
? 'apps/okr/domain/input/okr-requirement.md'
|
||||||
|
: 'docs/input/okr-requirement.md', 'utf8');
|
||||||
const architecture = readFileSync('docs/technical_architecture.md', 'utf8');
|
const architecture = readFileSync('docs/technical_architecture.md', 'utf8');
|
||||||
|
|
||||||
function write(path, content) {
|
function write(path, content) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { appendFileSync, copyFileSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
import { existsSync, appendFileSync, copyFileSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import { enabled, log, redact, LOG_LEVEL } from './casan-log.mjs';
|
import { enabled, log, redact, LOG_LEVEL } from './casan-log.mjs';
|
||||||
|
|
||||||
@@ -10,6 +10,10 @@ 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.
|
||||||
|
const HARNESS = existsSync(join(root, 'packages/casan-harness/scripts/bash/casan-harness.sh'))
|
||||||
|
? 'packages/casan-harness/scripts/bash/casan-harness.sh'
|
||||||
|
: '.specify/scripts/bash/casan-harness.sh';
|
||||||
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`;
|
||||||
@@ -172,7 +176,7 @@ function runHarness({ id, agent, step, attempt = '1' }) {
|
|||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
try {
|
try {
|
||||||
execFileSync(
|
execFileSync(
|
||||||
'.specify/scripts/bash/casan-harness.sh',
|
HARNESS,
|
||||||
[input, output, `agent_step_${id}`, '--', 'node', 'scripts/casan-step.mjs', step, attempt],
|
[input, output, `agent_step_${id}`, '--', 'node', 'scripts/casan-step.mjs', step, attempt],
|
||||||
{
|
{
|
||||||
cwd: root,
|
cwd: root,
|
||||||
@@ -245,7 +249,7 @@ function runDryStep({ diagram, agent, attempt = '1', verdict = 'APPROVED', extra
|
|||||||
const stubCmd = `printf '# %s dry-run stub artifact\\n\\nverdict: %s\\n' '${diagram}' '${verdict}' > "$CASAN_OUTPUT"`;
|
const stubCmd = `printf '# %s dry-run stub artifact\\n\\nverdict: %s\\n' '${diagram}' '${verdict}' > "$CASAN_OUTPUT"`;
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
execFileSync(
|
execFileSync(
|
||||||
'.specify/scripts/bash/casan-harness.sh',
|
HARNESS,
|
||||||
[input, output, `agent_step_${id}`, '--', 'bash', '-c', stubCmd],
|
[input, output, `agent_step_${id}`, '--', 'bash', '-c', stubCmd],
|
||||||
{
|
{
|
||||||
cwd: root,
|
cwd: root,
|
||||||
|
|||||||
Reference in New Issue
Block a user