feat: pack-hunt safety + opt-in Kali+ arsenal + capability-approval gate + tester-feedback UX

Adds three coordinated capabilities and tightens two defaults on purpose — this is
safety hardening, NOT a backward-compatible drop-in (see "Tightened defaults").
Fifteen independent adversarial review passes; findings fixed and test-pinned.

- Pack-hunt safety: a strict-majority adjudicator with a comment/string/regex/
  raw-string/heredoc/POD-aware cite-check (language-gated across C/C++/Py/Rust/Go/
  Java/JS/TS/shell/SQL/PHP/Ruby/Perl) so a hallucinated killing-guard can't refute
  a real finding — the cited comparison's operator must match source, and a
  string/regex/heredoc/generic-arglist/template-interpolation is blanked before
  matching. Plus a rate-limit/degraded-health tracker, a global LLM semaphore, and
  a PackBoard with an atomic claim CAS + snapshot-on-emit/clone-on-ingest.
- Opt-in Kali+ arsenal (T3MP3ST_FULL_ARSENAL, OFF by default): 35 default tools
  (31 built-in + 4 external-CLI wrappers) -> 83 with the catalog armed (verify-claims
  CLAIM 4). Most adapters need the underlying binary installed. metasploit / hydra /
  bloodhound are never minted through the generic factory.
- Capability-approval + spicy-action gate for the NEW dangerous arsenal only
  (pre-existing built-ins keep running freely): approve-once, fail-safe deny when
  unattended, redacted audit trail over SSE. T3MP3ST_GATE_BUILTINS=1 extends it to
  the spicy built-in probes. Audit redaction is a pure, unit-tested module.

Tightened defaults (intentional, ON by default — NOT backward-compatible):
- Egress containment: once a mission target is set, built-in networked tools refuse
  off-scope public hosts (not the target/subdomains, not loopback/private) with
  SCOPE DENIED — keeps the agent on authorized scope.
- Role-specialized operator toolkits: each archetype gets a curated default tool
  menu (scanner -> scan tools, exploiter -> sqli/xss/etc). Tools stay registered
  team-wide; the per-role menu is the operator's working set.

UX (from live-tester feedback): a one-click "Approve & run" modal when a standard
Hunt blocks on an external-target receipt (was a dead-end); bounded-parallel
benchmark file ingestion; the deterministic mission-gate chip co-located with the
subjective SITREP pace narration (no more "behind schedule" next to a clean gate);
and click-through finding detail + evidence in the UI (was export-only).

Docs: README + INTEGRITY_LEDGER made re-derivable — honest 35/83 tool count, the
egress-containment default documented, and the Cybench number reconciled to the
single verify-claims-enforced 23/40 = 58% (dropped the stale/uncited 21 and 26).
README voice pass: killed the underselling `Successful Exploits: 0` disqualifier
(read as "never popped anything" — false, given CVE-Zero + the live disclosure
pipeline); swagger now rests on receipts, not vibes. Also corrected the operator
status — the downstream operators run the SAME real tool-backed ReAct loop as
recon (not "orchestration stubs"); the honest limit is that the coordinated swarm
is unproven (single-agent is the benchmarked path), not that they're scaffolding.

Gates: 292 tests, tsc + lint clean, verify-claims 20/20, arsenal:smoke 125/0,
disclosure-CLI self-test 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
t3mp3st contributorsandClaude Opus 4.8 committed 2026-07-04 09:38:23 -04:00
1 parent 5255000cb6
commit 06ffe3ef66
35 files changed
+5019 -211

No files matched your search

+23 -23
View File
@@ -101,7 +101,7 @@ async function smokeCoreSurfaces() {
async function smokeArsenalCatalog() {
const catalog = await get('/api/arsenal/catalog');
const adapterIds = (catalog.data.adapters || []).map(adapter => adapter.id);
record('Arsenal catalog exposes adapter spine', catalog.ok && catalog.data.schema_version === 'plinyos.t3mp3st_arsenal_catalog/v1' && adapterIds.includes('nuclei') && adapterIds.includes('semgrep') && adapterIds.includes('promptfoo'), `${adapterIds.length} adapters`);
record('Arsenal catalog exposes adapter spine', catalog.ok && catalog.data.schema_version === 't3mp3st.arsenal_catalog/v1' && adapterIds.includes('nuclei') && adapterIds.includes('semgrep') && adapterIds.includes('promptfoo'), `${adapterIds.length} adapters`);
record('Arsenal safe commands exclude catalog-only heavy tools', catalog.ok && (catalog.data.safeCommands || []).includes('nuclei') && !(catalog.data.safeCommands || []).includes('msfconsole'), `${countItems(catalog.data.safeCommands)} safe commands`);
const webCatalog = await get('/api/arsenal/catalog?family=web_api');
@@ -113,7 +113,7 @@ async function smokeArsenalCatalog() {
record('Supply-chain arsenal includes evidence scanners', supplyCatalog.ok && ['semgrep', 'gitleaks', 'trivy', 'syft', 'grype'].every(id => supplyIds.includes(id)), supplyIds.join(', '));
const status = await get('/api/arsenal/status?family=web_api');
record('Arsenal status reports installed and missing adapters', status.ok && status.data.schema_version === 'plinyos.t3mp3st_arsenal_status/v1' && typeof status.data.summary?.readiness === 'number' && Array.isArray(status.data.missingCommandReady), `${status.data.summary?.installed || 0} installed / ${status.data.summary?.readiness ?? 'n/a'}%`);
record('Arsenal status reports installed and missing adapters', status.ok && status.data.schema_version === 't3mp3st.arsenal_status/v1' && typeof status.data.summary?.readiness === 'number' && Array.isArray(status.data.missingCommandReady), `${status.data.summary?.installed || 0} installed / ${status.data.summary?.readiness ?? 'n/a'}%`);
const plan = await post('/api/arsenal/plan', {
family: 'web_api',
@@ -121,7 +121,7 @@ async function smokeArsenalCatalog() {
objective: 'Plan a local-only web/API smoke toolchain with evidence flow.',
});
const planIds = (plan.data.steps || []).map(step => step.adapterId);
record('Arsenal planner turns adapters into gated evidence steps', plan.ok && plan.data.schema_version === 'plinyos.t3mp3st_arsenal_plan/v1' && planIds.includes('nuclei') && (plan.data.steps || []).every(step => step.gate && step.nextEvidenceMove), `${countItems(plan.data.steps)} planned steps`);
record('Arsenal planner turns adapters into gated evidence steps', plan.ok && plan.data.schema_version === 't3mp3st.arsenal_plan/v1' && planIds.includes('nuclei') && (plan.data.steps || []).every(step => step.gate && step.nextEvidenceMove), `${countItems(plan.data.steps)} planned steps`);
}
async function smokeKnowledgeAtlas() {
@@ -190,7 +190,7 @@ async function smokeContractsAndLedgers() {
const route = await post('/api/route-preview', { draftId: draft.data.id });
const operationDraft = route.data.operationDraft || {};
record('Route preview emits operation contract', route.ok && operationDraft.schema_version === 'plinyos.t3mp3st_operation/v1', operationDraft.schema_version || summarizeError(route));
record('Route preview emits operation contract', route.ok && operationDraft.schema_version === 't3mp3st.operation/v1', operationDraft.schema_version || summarizeError(route));
const missionApprovalRequest = await post('/api/approvals/request', {
action: 'mission_execution',
@@ -263,7 +263,7 @@ async function smokeContractsAndLedgers() {
record('Hypothesis ledger can be filtered by mission', hypothesisList.ok && (hypothesisList.data.hypotheses || []).some(item => item.id === hypothesis.data.id), `${countItems(hypothesisList.data.hypotheses)} hypotheses`);
const evidenceGraph = await get(`/api/evidence-graph?missionId=${encodeURIComponent(draft.data.id)}&operationId=${encodeURIComponent(operationDraft.operation_id)}&family=web_api`);
record('Evidence graph links hypotheses and proof', evidenceGraph.ok && evidenceGraph.data.schema_version === 'plinyos.t3mp3st_evidence_graph/v1' && countItems(evidenceGraph.data.nodes) >= 2 && countItems(evidenceGraph.data.edges) >= 1, `${countItems(evidenceGraph.data.nodes)} nodes / ${countItems(evidenceGraph.data.edges)} edges`);
record('Evidence graph links hypotheses and proof', evidenceGraph.ok && evidenceGraph.data.schema_version === 't3mp3st.evidence_graph/v1' && countItems(evidenceGraph.data.nodes) >= 2 && countItems(evidenceGraph.data.edges) >= 1, `${countItems(evidenceGraph.data.nodes)} nodes / ${countItems(evidenceGraph.data.edges)} edges`);
const crossFamilyHypothesis = await post('/api/hypotheses', {
missionId: draft.data.id,
@@ -290,7 +290,7 @@ async function smokeContractsAndLedgers() {
const decomposition = hypothesis.data.id
? await post(`/api/hypotheses/${hypothesis.data.id}/decompose`, {})
: { status: 0, data: {} };
record('Hypothesis decomposes into specialist work orders', decomposition.status === 201 && decomposition.data.schema_version === 'plinyos.t3mp3st_work_order_decomposition/v1' && countItems(decomposition.data.workOrders) >= 5, `${countItems(decomposition.data.workOrders)} work orders`);
record('Hypothesis decomposes into specialist work orders', decomposition.status === 201 && decomposition.data.schema_version === 't3mp3st.work_order_decomposition/v1' && countItems(decomposition.data.workOrders) >= 5, `${countItems(decomposition.data.workOrders)} work orders`);
const workOrderList = await get(`/api/work-orders?missionId=${encodeURIComponent(draft.data.id)}&operationId=${encodeURIComponent(operationDraft.operation_id)}&family=web_api`);
record('Work order queue can be filtered by mission', workOrderList.ok && countItems(workOrderList.data.workOrders) >= 5, `${countItems(workOrderList.data.workOrders)} queued`);
@@ -329,7 +329,7 @@ async function smokeContractsAndLedgers() {
target: 'local-lab',
spawnWorkOrders: false,
});
record('Watch loop emits scoped signals', watchPulse.status === 201 && watchPulse.data.schema_version === 'plinyos.t3mp3st_watch_loop_cycle/v1' && countItems(watchPulse.data.signals) >= 1, `${watchPulse.data.summary?.signals || 0} signals / ${watchPulse.data.summary?.actions || 0} actions`);
record('Watch loop emits scoped signals', watchPulse.status === 201 && watchPulse.data.schema_version === 't3mp3st.watch_loop_cycle/v1' && countItems(watchPulse.data.signals) >= 1, `${watchPulse.data.summary?.signals || 0} signals / ${watchPulse.data.summary?.actions || 0} actions`);
const watchNudge = await post('/api/watch-loop/run', {
missionId: draft.data.id,
@@ -338,11 +338,11 @@ async function smokeContractsAndLedgers() {
target: 'local-lab',
spawnWorkOrders: true,
});
record('Watch loop nudge spawns specialist work orders', watchNudge.status === 201 && watchNudge.data.schema_version === 'plinyos.t3mp3st_watch_loop_cycle/v1' && (watchNudge.data.summary?.spawnedWorkOrders || 0) >= 5, `${watchNudge.data.summary?.spawnedWorkOrders || 0} spawned`);
record('Watch loop nudge spawns specialist work orders', watchNudge.status === 201 && watchNudge.data.schema_version === 't3mp3st.watch_loop_cycle/v1' && (watchNudge.data.summary?.spawnedWorkOrders || 0) >= 5, `${watchNudge.data.summary?.spawnedWorkOrders || 0} spawned`);
const watchStatus = await get(`/api/watch-loop/status?missionId=${encodeURIComponent(draft.data.id)}&operationId=${encodeURIComponent(operationDraft.operation_id)}&family=web_api&target=local-lab`);
const watchCycleIds = (watchStatus.data.cycles || []).map(cycle => cycle.id);
record('Watch loop status recalls latest pulse', watchStatus.ok && watchStatus.data.schema_version === 'plinyos.t3mp3st_watch_loop_status/v1' && watchCycleIds.includes(watchNudge.data.id), `${watchStatus.data.latestCycle?.id || 'none'} latest / ${watchCycleIds.length} retained`);
record('Watch loop status recalls latest pulse', watchStatus.ok && watchStatus.data.schema_version === 't3mp3st.watch_loop_status/v1' && watchCycleIds.includes(watchNudge.data.id), `${watchStatus.data.latestCycle?.id || 'none'} latest / ${watchCycleIds.length} retained`);
const selfHeal = await post('/api/self-heal/run', {
missionId: draft.data.id,
@@ -352,7 +352,7 @@ async function smokeContractsAndLedgers() {
operationDraft,
apply: true,
});
record('Self-heal diagnoses and safely applies watch repair', selfHeal.status === 201 && selfHeal.data.schema_version === 'plinyos.t3mp3st_self_heal/v1' && countItems(selfHeal.data.actions) >= 1 && typeof selfHeal.data.summary?.applied === 'number', `${selfHeal.data.health || 'unknown'} / ${selfHeal.data.summary?.applied || 0} applied`);
record('Self-heal diagnoses and safely applies watch repair', selfHeal.status === 201 && selfHeal.data.schema_version === 't3mp3st.self_heal/v1' && countItems(selfHeal.data.actions) >= 1 && typeof selfHeal.data.summary?.applied === 'number', `${selfHeal.data.health || 'unknown'} / ${selfHeal.data.summary?.applied || 0} applied`);
const supportedHypothesis = hypothesis.data.id
? await patch(`/api/hypotheses/${hypothesis.data.id}`, { status: 'supported', confidence: 0.76 })
@@ -400,7 +400,7 @@ async function smokeContractsAndLedgers() {
record('Retest can be queued', retest.status === 201 && retest.data.id, retest.data.id || summarizeError(retest));
const gateWithQueuedRetest = await post('/api/mission-gate', { operationDraft });
record('Mission gate holds unresolved retest', gateWithQueuedRetest.ok && gateWithQueuedRetest.data.schema_version === 'plinyos.t3mp3st_mission_gate/v1' && gateWithQueuedRetest.data.status !== 'ready', `${gateWithQueuedRetest.data.status || 'unknown'} ${gateWithQueuedRetest.data.score ?? 'n/a'}/100`);
record('Mission gate holds unresolved retest', gateWithQueuedRetest.ok && gateWithQueuedRetest.data.schema_version === 't3mp3st.mission_gate/v1' && gateWithQueuedRetest.data.status !== 'ready', `${gateWithQueuedRetest.data.status || 'unknown'} ${gateWithQueuedRetest.data.score ?? 'n/a'}/100`);
const completedRetest = retest.data.id
? await patch(`/api/retests/${retest.data.id}`, { status: 'passed', resultSummary: 'Arsenal smoke retest passed.' })
@@ -409,12 +409,12 @@ async function smokeContractsAndLedgers() {
const reproPacks = await post('/api/repro-packs', { operationDraft });
const readyReproPack = (reproPacks.data.packs || []).find(pack => pack.readiness === 'ready');
record('Repro packs summarize replay readiness', reproPacks.ok && reproPacks.data.schema_version === 'plinyos.t3mp3st_repro_packs/v1' && (reproPacks.data.summary?.total || 0) >= 2 && (reproPacks.data.summary?.ready || 0) >= 1, `${reproPacks.data.summary?.ready || 0}/${reproPacks.data.summary?.total || 0} ready`);
record('Repro packs summarize replay readiness', reproPacks.ok && reproPacks.data.schema_version === 't3mp3st.repro_packs/v1' && (reproPacks.data.summary?.total || 0) >= 2 && (reproPacks.data.summary?.ready || 0) >= 1, `${reproPacks.data.summary?.ready || 0}/${reproPacks.data.summary?.total || 0} ready`);
record('Repro pack includes safe replay contract', Boolean(readyReproPack && countItems(readyReproPack.replaySteps) >= 4 && countItems(readyReproPack.falsifiers) >= 3 && readyReproPack.safeProbe), readyReproPack?.id || summarizeError(reproPacks));
const pressurePaths = await post('/api/pressure-paths', { operationDraft });
const bestPressurePath = (pressurePaths.data.paths || [])[0];
record('Pressure paths convert repro into gated offensive chain', pressurePaths.ok && pressurePaths.data.schema_version === 'plinyos.t3mp3st_pressure_paths/v1' && (pressurePaths.data.summary?.total || 0) >= 2 && (pressurePaths.data.summary?.maxOffensiveScore || 0) > 0, `${pressurePaths.data.summary?.armed || 0}/${pressurePaths.data.summary?.total || 0} armed, max ${pressurePaths.data.summary?.maxOffensiveScore || 0}`);
record('Pressure paths convert repro into gated offensive chain', pressurePaths.ok && pressurePaths.data.schema_version === 't3mp3st.pressure_paths/v1' && (pressurePaths.data.summary?.total || 0) >= 2 && (pressurePaths.data.summary?.maxOffensiveScore || 0) > 0, `${pressurePaths.data.summary?.armed || 0}/${pressurePaths.data.summary?.total || 0} armed, max ${pressurePaths.data.summary?.maxOffensiveScore || 0}`);
record('Pressure path includes simulator and no-go gates', Boolean(bestPressurePath && bestPressurePath.safeSimulator?.mode === 'local_canary' && countItems(bestPressurePath.chainStages) >= 5 && countItems(bestPressurePath.noGo) >= 2), bestPressurePath?.id || summarizeError(pressurePaths));
const pressureCanary = await post('/api/pressure-paths/canary', {
@@ -422,7 +422,7 @@ async function smokeContractsAndLedgers() {
pathId: bestPressurePath?.id,
pressurePaths: pressurePaths.data,
});
record('Pressure canary rehearses top path locally', pressureCanary.ok && pressureCanary.data.schema_version === 'plinyos.t3mp3st_pressure_canary/v1' && pressureCanary.data.status === 'passed' && pressureCanary.data.canary?.mode === 'local_canary', pressureCanary.data.canary?.observedSignal || summarizeError(pressureCanary));
record('Pressure canary rehearses top path locally', pressureCanary.ok && pressureCanary.data.schema_version === 't3mp3st.pressure_canary/v1' && pressureCanary.data.status === 'passed' && pressureCanary.data.canary?.mode === 'local_canary', pressureCanary.data.canary?.observedSignal || summarizeError(pressureCanary));
// Honest by design: a local synthetic canary is CONTEXT evidence, never 'replayable'
// proof against the real target (see buildPressureCanary — stamping it 'replayable'
// would let a finding self-promote on fabricated evidence). Assert 'context'.
@@ -434,7 +434,7 @@ async function smokeContractsAndLedgers() {
pressurePaths: pressurePaths.data,
pressureCanary: pressureCanary.data,
});
record('Pressure duel survives only evidence-backed canary route', pressureDuel.ok && pressureDuel.data.schema_version === 'plinyos.t3mp3st_pressure_duel/v1' && pressureDuel.data.status === 'survived' && (pressureDuel.data.survivabilityScore || 0) >= 86, `${pressureDuel.data.status || 'unknown'} ${pressureDuel.data.survivabilityScore || 0}/100`);
record('Pressure duel survives only evidence-backed canary route', pressureDuel.ok && pressureDuel.data.schema_version === 't3mp3st.pressure_duel/v1' && pressureDuel.data.status === 'survived' && (pressureDuel.data.survivabilityScore || 0) >= 86, `${pressureDuel.data.status || 'unknown'} ${pressureDuel.data.survivabilityScore || 0}/100`);
record('Pressure duel writes skeptic evidence and follow-up task', Boolean(pressureDuel.data.evidence?.id && pressureDuel.data.workOrder?.id && countItems(pressureDuel.data.duel?.rounds) >= 3), `${pressureDuel.data.evidence?.id || 'no evidence'} / ${pressureDuel.data.workOrder?.id || 'no work order'}`);
const pressureMutations = await post('/api/pressure-paths/mutate', {
@@ -443,7 +443,7 @@ async function smokeContractsAndLedgers() {
pressurePaths: pressurePaths.data,
pressureDuel: pressureDuel.data,
});
record('Pressure mutation gauntlet forks survived route', pressureMutations.ok && pressureMutations.data.schema_version === 'plinyos.t3mp3st_pressure_mutations/v1' && pressureMutations.data.status === 'queued' && countItems(pressureMutations.data.mutations) >= 5 && (pressureMutations.data.summary?.maxFangScore || 0) >= 80, `${countItems(pressureMutations.data.mutations)} mutations / max ${pressureMutations.data.summary?.maxFangScore || 0}`);
record('Pressure mutation gauntlet forks survived route', pressureMutations.ok && pressureMutations.data.schema_version === 't3mp3st.pressure_mutations/v1' && pressureMutations.data.status === 'queued' && countItems(pressureMutations.data.mutations) >= 5 && (pressureMutations.data.summary?.maxFangScore || 0) >= 80, `${countItems(pressureMutations.data.mutations)} mutations / max ${pressureMutations.data.summary?.maxFangScore || 0}`);
record('Pressure mutation gauntlet queues specialist work orders', Boolean(pressureMutations.data.evidence?.id && countItems(pressureMutations.data.workOrders) >= 3 && pressureMutations.data.workOrders.every(order => order.requiresReceipt === false)), `${pressureMutations.data.evidence?.id || 'no evidence'} / ${countItems(pressureMutations.data.workOrders)} work orders`);
const pressureChains = await post('/api/pressure-paths/chains', {
@@ -452,7 +452,7 @@ async function smokeContractsAndLedgers() {
pressurePaths: pressurePaths.data,
pressureMutations: pressureMutations.data,
});
record('Pressure fang chains compose local weird-machine routes', pressureChains.ok && pressureChains.data.schema_version === 'plinyos.t3mp3st_pressure_chains/v1' && pressureChains.data.status === 'queued' && countItems(pressureChains.data.chains) >= 3 && (pressureChains.data.summary?.maxChainScore || 0) >= 80, `${countItems(pressureChains.data.chains)} chains / max ${pressureChains.data.summary?.maxChainScore || 0}`);
record('Pressure fang chains compose local weird-machine routes', pressureChains.ok && pressureChains.data.schema_version === 't3mp3st.pressure_chains/v1' && pressureChains.data.status === 'queued' && countItems(pressureChains.data.chains) >= 3 && (pressureChains.data.summary?.maxChainScore || 0) >= 80, `${countItems(pressureChains.data.chains)} chains / max ${pressureChains.data.summary?.maxChainScore || 0}`);
record('Pressure fang chains queue staged specialist work orders', Boolean(pressureChains.data.evidence?.id && countItems(pressureChains.data.workOrders) >= 2 && pressureChains.data.workOrders.every(order => order.requiresReceipt === false)), `${pressureChains.data.evidence?.id || 'no evidence'} / ${countItems(pressureChains.data.workOrders)} work orders`);
const evidenceList = await get(`/api/evidence?missionId=${encodeURIComponent(draft.data.id)}`);
@@ -469,15 +469,15 @@ async function smokeContractsAndLedgers() {
record('Retest ledger can be listed', retestList.ok && (retestList.data.retests || []).some(item => item.id === retest.data.id), `${countItems(retestList.data.retests)} retests`);
const bundle = await post('/api/mission-bundles', { operationDraft });
record('Mission bundle exports handoff', bundle.ok && bundle.data.schema_version === 'plinyos.t3mp3st_mission_bundle/v1', bundle.data.handoff?.humanSummary || summarizeError(bundle));
record('Mission bundle carries repro packs', bundle.ok && bundle.data.reproPacks?.schema_version === 'plinyos.t3mp3st_repro_packs/v1' && (bundle.data.reproPacks.summary?.total || 0) >= 2, `${bundle.data.reproPacks?.summary?.ready || 0}/${bundle.data.reproPacks?.summary?.total || 0} ready`);
record('Mission bundle carries pressure paths', bundle.ok && bundle.data.pressurePaths?.schema_version === 'plinyos.t3mp3st_pressure_paths/v1' && (bundle.data.pressurePaths.summary?.total || 0) >= 2, `${bundle.data.pressurePaths?.summary?.armed || 0}/${bundle.data.pressurePaths?.summary?.total || 0} armed`);
record('Mission bundle exports handoff', bundle.ok && bundle.data.schema_version === 't3mp3st.mission_bundle/v1', bundle.data.handoff?.humanSummary || summarizeError(bundle));
record('Mission bundle carries repro packs', bundle.ok && bundle.data.reproPacks?.schema_version === 't3mp3st.repro_packs/v1' && (bundle.data.reproPacks.summary?.total || 0) >= 2, `${bundle.data.reproPacks?.summary?.ready || 0}/${bundle.data.reproPacks?.summary?.total || 0} ready`);
record('Mission bundle carries pressure paths', bundle.ok && bundle.data.pressurePaths?.schema_version === 't3mp3st.pressure_paths/v1' && (bundle.data.pressurePaths.summary?.total || 0) >= 2, `${bundle.data.pressurePaths?.summary?.armed || 0}/${bundle.data.pressurePaths?.summary?.total || 0} armed`);
const bundleByMission = await get(`/api/mission-bundles/${encodeURIComponent(draft.data.id)}`);
record('Mission bundle can be rebuilt by mission id', bundleByMission.ok && bundleByMission.data.schema_version === 'plinyos.t3mp3st_mission_bundle/v1', bundleByMission.data.handoff?.humanSummary || summarizeError(bundleByMission));
record('Mission bundle can be rebuilt by mission id', bundleByMission.ok && bundleByMission.data.schema_version === 't3mp3st.mission_bundle/v1', bundleByMission.data.handoff?.humanSummary || summarizeError(bundleByMission));
const gate = await post('/api/mission-gate', { operationDraft });
record('Mission gate evaluates ledgers and receipts', gate.ok && gate.data.schema_version === 'plinyos.t3mp3st_mission_gate/v1' && ['hold', 'ready'].includes(gate.data.status), `${gate.data.status || 'unknown'} ${gate.data.score ?? 'n/a'}/100`);
record('Mission gate evaluates ledgers and receipts', gate.ok && gate.data.schema_version === 't3mp3st.mission_gate/v1' && ['hold', 'ready'].includes(gate.data.status), `${gate.data.status || 'unknown'} ${gate.data.score ?? 'n/a'}/100`);
const tempDraft = await post('/api/mission-drafts', {
title: 'Arsenal Smoke Delete Fixture',
@@ -561,7 +561,7 @@ async function smokeImprovementAndLearning(context) {
record('Repeated learning review reinforces, not duplicates', repeated.status === 201 && beforeCount === afterCount && reinforced, `${afterCount} proposals / reinforced=${reinforced}`);
const capsule = await get('/api/memory/capsule');
record('Memory capsule exposes accepted entries only', capsule.ok && capsule.data.schema_version === 'plinyos.t3mp3st_memory_capsule/v1' && (capsule.data.entries || []).some(item => item.id === acceptedMemory.data.entry?.id), `${countItems(capsule.data.entries)} entries`);
record('Memory capsule exposes accepted entries only', capsule.ok && capsule.data.schema_version === 't3mp3st.memory_capsule/v1' && (capsule.data.entries || []).some(item => item.id === acceptedMemory.data.entry?.id), `${countItems(capsule.data.entries)} entries`);
}
// NOTE: The "Pliny Specials" backend routes (/api/pliny/leviathan/engage,
+2 -2
View File
@@ -120,10 +120,10 @@ async function main() {
const preflight = await apiGet('/api/preflight');
check('Capability preflight', preflight.ok && typeof preflight.data.score === 'number', `${preflight.data.score ?? 'n/a'}/100`);
const arsenal = await apiGet('/api/arsenal/status');
check('Arsenal status', arsenal.ok && arsenal.data.schema_version === 'plinyos.t3mp3st_arsenal_status/v1', `${arsenal.data.summary?.installedCommandReady ?? 0}/${arsenal.data.summary?.commandReady ?? 0} installed`);
check('Arsenal status', arsenal.ok && arsenal.data.schema_version === 't3mp3st.arsenal_status/v1', `${arsenal.data.summary?.installedCommandReady ?? 0}/${arsenal.data.summary?.commandReady ?? 0} installed`);
check('Arsenal does not fake empty coverage', arsenal.ok && arsenal.data.summary?.unmodeled === false, `unmodeled=${arsenal.data.summary?.unmodeled}`);
const activation = await apiGet('/api/arsenal/activation');
check('Arsenal activation plan', activation.ok && activation.data.schema_version === 'plinyos.t3mp3st_arsenal_activation/v1', `${activation.data.summary?.total || 0} wired / doc ${activation.data.localPlanDoc || 'missing'}`);
check('Arsenal activation plan', activation.ok && activation.data.schema_version === 't3mp3st.arsenal_activation/v1', `${activation.data.summary?.total || 0} wired / doc ${activation.data.localPlanDoc || 'missing'}`);
}
const blocks = checks.filter(item => !item.passed && item.severity === 'block');
+5 -5
View File
@@ -70,7 +70,7 @@ async function main() {
? await post('/api/route-preview', { draftId: draft.data.id })
: { ok: false, data: {} };
const operationDraft = route.data.operationDraft || {};
record('Route preview emits operation contract', route.ok && operationDraft.schema_version === 'plinyos.t3mp3st_operation/v1', operationDraft.schema_version || route.data.error);
record('Route preview emits operation contract', route.ok && operationDraft.schema_version === 't3mp3st.operation/v1', operationDraft.schema_version || route.data.error);
record('Route family is web/API for synthetic local range', route.ok && route.data.route?.family === 'web_api', route.data.route?.family || route.data.error);
const approvalRequest = await post('/api/approvals/request', {
@@ -154,17 +154,17 @@ async function main() {
record('Retest completed for full-chain smoke', completedRetest.ok && completedRetest.data.status === 'passed', completedRetest.data.status || completedRetest.data.error);
const bundle = await post('/api/mission-bundles', { operationDraft });
record('Mission bundle includes exploit-chain ledgers', bundle.ok && bundle.data.schema_version === 'plinyos.t3mp3st_mission_bundle/v1' && countItems(bundle.data.ledgers?.evidence) > 0 && countItems(bundle.data.ledgers?.findings) > 0, bundle.data.handoff?.humanSummary || bundle.data.error);
record('Mission bundle includes exploit-chain ledgers', bundle.ok && bundle.data.schema_version === 't3mp3st.mission_bundle/v1' && countItems(bundle.data.ledgers?.evidence) > 0 && countItems(bundle.data.ledgers?.findings) > 0, bundle.data.handoff?.humanSummary || bundle.data.error);
const gate = await post('/api/mission-gate', { operationDraft });
record('Mission gate is ready after receipt, evidence, and retest', gate.ok && gate.data.schema_version === 'plinyos.t3mp3st_mission_gate/v1' && gate.data.status === 'ready' && gate.data.score >= 80, `${gate.data.status || 'unknown'} ${gate.data.score ?? 'n/a'}/100`);
record('Mission gate is ready after receipt, evidence, and retest', gate.ok && gate.data.schema_version === 't3mp3st.mission_gate/v1' && gate.data.status === 'ready' && gate.data.score >= 80, `${gate.data.status || 'unknown'} ${gate.data.score ?? 'n/a'}/100`);
const learningReview = await post('/api/learning/run-review', {
missionId: operationDraft.mission_id,
operationId: operationDraft.operation_id,
family: operationDraft.family,
});
record('Learning review proposes inspectable memory', learningReview.status === 201 && learningReview.data.schema_version === 'plinyos.t3mp3st_learning_review/v1' && countItems(learningReview.data.proposals) > 0, `${countItems(learningReview.data.proposals)} proposal(s)`);
record('Learning review proposes inspectable memory', learningReview.status === 201 && learningReview.data.schema_version === 't3mp3st.learning_review/v1' && countItems(learningReview.data.proposals) > 0, `${countItems(learningReview.data.proposals)} proposal(s)`);
const proposalId = learningReview.data.proposals?.[0]?.id;
const acceptedMemory = proposalId
@@ -173,7 +173,7 @@ async function main() {
record('Memory proposal can be explicitly accepted', acceptedMemory.ok && acceptedMemory.data.entry?.id && acceptedMemory.data.proposal?.status === 'accepted', acceptedMemory.data.entry?.id || acceptedMemory.data.error);
const capsule = await get('/api/memory/capsule');
record('Memory capsule exposes accepted entries', capsule.ok && capsule.data.schema_version === 'plinyos.t3mp3st_memory_capsule/v1' && (capsule.data.entries || []).some(entry => entry.id === acceptedMemory.data.entry?.id), `${countItems(capsule.data.entries)} accepted memory entries`);
record('Memory capsule exposes accepted entries', capsule.ok && capsule.data.schema_version === 't3mp3st.memory_capsule/v1' && (capsule.data.entries || []).some(entry => entry.id === acceptedMemory.data.entry?.id), `${countItems(capsule.data.entries)} accepted memory entries`);
const proposalListBeforeRepeat = await get('/api/memory/proposals');
const proposalCountBeforeRepeat = countItems(proposalListBeforeRepeat.data.proposals);
+7 -7
View File
@@ -90,7 +90,7 @@ async function runScenario(name, scenario) {
const routePreview = await post('/api/route-preview', { draftId: missionDraft.data.id });
record(`[${name}] route family classified`, routePreview.ok && routePreview.data.route?.family === scenario.family, routePreview.data.route?.family || routePreview.data.error);
record(`[${name}] operation contract emitted`, routePreview.data.operationDraft?.schema_version === 'plinyos.t3mp3st_operation/v1', routePreview.data.operationDraft?.schema_version || 'missing schema');
record(`[${name}] operation contract emitted`, routePreview.data.operationDraft?.schema_version === 't3mp3st.operation/v1', routePreview.data.operationDraft?.schema_version || 'missing schema');
record(`[${name}] operation contract carries runbook context`, Boolean(routePreview.data.operationDraft?.knowledge_context?.operator_runbook), routePreview.data.operationDraft?.knowledge_context?.operator_runbook || 'missing runbook context');
const evidence = await post('/api/evidence', {
@@ -135,10 +135,10 @@ async function runScenario(name, scenario) {
record(`[${name}] retest can complete`, completedRetest.ok && completedRetest.data.status === 'passed', completedRetest.data.status || completedRetest.data.error);
const bundle = await post('/api/mission-bundles', { operationDraft: routePreview.data.operationDraft });
record(`[${name}] mission bundle exports handoff`, bundle.ok && bundle.data.schema_version === 'plinyos.t3mp3st_mission_bundle/v1' && bundle.data.runbook?.family === scenario.family && bundle.data.promptPacks?.some(pack => pack.id === scenario.expectedPromptPack), bundle.data.handoff?.humanSummary || bundle.data.error);
record(`[${name}] mission bundle exports handoff`, bundle.ok && bundle.data.schema_version === 't3mp3st.mission_bundle/v1' && bundle.data.runbook?.family === scenario.family && bundle.data.promptPacks?.some(pack => pack.id === scenario.expectedPromptPack), bundle.data.handoff?.humanSummary || bundle.data.error);
const gate = await post('/api/mission-gate', { operationDraft: routePreview.data.operationDraft });
record(`[${name}] mission gate reports launch status`, gate.ok && gate.data.schema_version === 'plinyos.t3mp3st_mission_gate/v1' && ['hold', 'ready'].includes(gate.data.status), `${gate.data.status || 'unknown'} ${gate.data.score ?? 'n/a'}/100`);
record(`[${name}] mission gate reports launch status`, gate.ok && gate.data.schema_version === 't3mp3st.mission_gate/v1' && ['hold', 'ready'].includes(gate.data.status), `${gate.data.status || 'unknown'} ${gate.data.score ?? 'n/a'}/100`);
return { missionDraft, routePreview, evidence, finding, retest };
}
@@ -203,19 +203,19 @@ async function main() {
}
const arsenalStatus = await get('/api/arsenal/status?family=web_api');
record('Arsenal adapter status reports mission readiness', arsenalStatus.ok && arsenalStatus.data.schema_version === 'plinyos.t3mp3st_arsenal_status/v1' && typeof arsenalStatus.data.summary?.readiness === 'number', `${arsenalStatus.data.summary?.readiness ?? 'n/a'}% ready`);
record('Arsenal adapter status reports mission readiness', arsenalStatus.ok && arsenalStatus.data.schema_version === 't3mp3st.arsenal_status/v1' && typeof arsenalStatus.data.summary?.readiness === 'number', `${arsenalStatus.data.summary?.readiness ?? 'n/a'}% ready`);
const arsenalPlan = await post('/api/arsenal/plan', {
family: 'web_api',
target: 'local-lab',
objective: 'Field drill validates gated tool planning.',
});
record('Arsenal planner emits gated evidence steps', arsenalPlan.ok && arsenalPlan.data.schema_version === 'plinyos.t3mp3st_arsenal_plan/v1' && (arsenalPlan.data.steps || []).length >= 4 && (arsenalPlan.data.stopConditions || []).length >= 2, `${arsenalPlan.data.steps?.length || 0} steps / ${arsenalPlan.data.stopConditions?.length || 0} stops`);
record('Arsenal planner emits gated evidence steps', arsenalPlan.ok && arsenalPlan.data.schema_version === 't3mp3st.arsenal_plan/v1' && (arsenalPlan.data.steps || []).length >= 4 && (arsenalPlan.data.stopConditions || []).length >= 2, `${arsenalPlan.data.steps?.length || 0} steps / ${arsenalPlan.data.stopConditions?.length || 0} stops`);
const doctrine = await get('/api/operator-doctrine');
record('Operator doctrine exposes authority model', doctrine.ok && /scope receipts/.test(doctrine.data.doctrine || '') && doctrine.data.authorityModel?.claimHardening === 'hypothesis -> evidence -> finding -> fix -> retest', doctrine.data.authorityModel?.claimHardening || doctrine.data.error);
const learningStatus = await get('/api/learning/status');
record('Learning loop exposes explicit proposal policy', learningStatus.ok && learningStatus.data.schema_version === 'plinyos.t3mp3st_learning_status/v1' && learningStatus.data.policy?.silentLearning === false && learningStatus.data.policy?.acceptanceRequired === true, learningStatus.data.policy?.proposalFlow || learningStatus.data.error);
record('Learning loop exposes explicit proposal policy', learningStatus.ok && learningStatus.data.schema_version === 't3mp3st.learning_status/v1' && learningStatus.data.policy?.silentLearning === false && learningStatus.data.policy?.acceptanceRequired === true, learningStatus.data.policy?.proposalFlow || learningStatus.data.error);
record('Learning loop deduplicates repeated reviews', learningStatus.ok && learningStatus.data.dedupe?.enabled === true && /canonical content fingerprint/.test(learningStatus.data.dedupe?.strategy || ''), learningStatus.data.dedupe?.strategy || learningStatus.data.error);
const runbooks = await get('/api/operator-runbooks');
@@ -223,7 +223,7 @@ async function main() {
const forefrontRadar = await get('/api/forefront-radar');
const laneIds = (forefrontRadar.data.lanes || []).map(lane => lane.id);
record('Forefront radar exposes pressure lanes', forefrontRadar.ok && forefrontRadar.data.schema_version === 'plinyos.t3mp3st_forefront_radar/v1' && laneIds.includes('frontier-agent-command-injection') && laneIds.includes('frontier-browser-tool-privilege'), laneIds.join(', '));
record('Forefront radar exposes pressure lanes', forefrontRadar.ok && forefrontRadar.data.schema_version === 't3mp3st.forefront_radar/v1' && laneIds.includes('frontier-agent-command-injection') && laneIds.includes('frontier-browser-tool-privilege'), laneIds.join(', '));
const cveSearch = await post('/api/resource-packs/search', { query: 'CVE' });
const cveResourceIds = (cveSearch.data.resources || []).map(resource => resource.id);
+267 -5
View File
@@ -113,16 +113,264 @@ function parseVerdict(text) {
// refuting a REAL finding. Before counting a REFUTED vote, confirm the cited guard's
// comparison actually appears in the cited source file. Catches the exact failure observed
// on the held wild-hunt targets: real findings refuted via invented or paraphrased-irrelevant guards.
//
// CANONICAL LOGIC: src/mission/adjudicate.ts `guardExistsInSource` — this is a self-contained mirror
// (the CLI must run offline). KEEP THE TWO IN SYNC. Hardened 2026-07-03: the structural fallback now
// requires the cited operands to be the operands of a REAL comparison in source (not two tokens that
// merely co-occur on a line), and the substring path requires the quote to actually LOOK like a guard
// (a comparison / control keyword / bounds-call). Without both, a hallucinated guard whose operand
// strings happen to appear in source could falsely refute — and permanently dedup-block — a real 0-day.
// Reduce source to code-only lines. Mirror of codeLinesOf + langTraits in src/mission/adjudicate.ts —
// keep in sync. Comment/string forms are stripped precisely PER LANGUAGE; the regex-vs-division call is
// avoided entirely — a regex-bearing language blanks each line past its first `/`, a no-regex language
// keeps divisions verbatim. See the TS copy's docstring for the full rationale.
function langTraits(file) {
const base = { regexAware: false, slashComment: false, blockComment: false, hashComment: false, dashComment: false, pctComment: false, splice: false, pyTriple: false, template: false, rawBacktick: false, cppRaw: false, rustRaw: false, heredoc: false, podDoc: false, perlQuote: false, dollarQuote: false };
const set = (o) => ({ ...base, ...o });
const ext = (String(file || '').toLowerCase().match(/\.([a-z0-9+#]+)$/) || [])[1] || '';
if (/^(js|jsx|mjs|cjs|ts|tsx|mts|cts)$/.test(ext)) return set({ regexAware: true, slashComment: true, blockComment: true, template: true });
if (/^(cpp|cxx|cc|hpp|hh|hxx|c\+\+|ino|cppm|ixx|tpp|ipp)$/.test(ext)) return set({ slashComment: true, blockComment: true, splice: true, cppRaw: true });
if (/^(c|h)$/.test(ext)) return set({ slashComment: true, blockComment: true, splice: true });
if (/^go$/.test(ext)) return set({ slashComment: true, blockComment: true, rawBacktick: true });
if (/^rs$/.test(ext)) return set({ slashComment: true, blockComment: true, rustRaw: true });
if (/^(java|kt|kts|scala|swift|cs|dart|groovy|gradle)$/.test(ext)) return set({ slashComment: true, blockComment: true, pyTriple: true });
if (/^(py|pyi|pyw|pyx)$/.test(ext)) return set({ hashComment: true, pyTriple: true });
if (/^(sh|bash|zsh|ksh)$/.test(ext)) return set({ hashComment: true, heredoc: 'shell' });
if (/^(fish|yaml|yml|tf|toml|ini|cfg|conf|dockerfile|mk|cmake|jl|nim|coffee|properties|gitignore)$/.test(ext)) return set({ hashComment: true });
if (/^php\d*$/.test(ext)) return set({ slashComment: true, blockComment: true, hashComment: true, heredoc: 'php' });
if (/^(sql|hql|psql|pgsql|ddl)$/.test(ext)) return set({ dashComment: true, blockComment: true, dollarQuote: true });
if (/^(lua|hs|lhs|elm|adb|ads)$/.test(ext)) return set({ dashComment: true });
if (/^(erl|hrl|tex|latex)$/.test(ext)) return set({ pctComment: true });
return set({ regexAware: true, slashComment: true, blockComment: true, hashComment: true, dashComment: true, pctComment: true, pyTriple: true, rawBacktick: true, heredoc: 'ruby', podDoc: true, perlQuote: true });
}
function codeLinesOf(src, t) {
const n = src.length;
let masked = '';
const heredocs = [];
const isIdent = (ch) => ch !== undefined && /[A-Za-z0-9_$]/.test(ch);
const blank = (len) => { if (len > 0) masked += ' '.repeat(len); };
const emitEsc = (i) => {
if (src[i + 1] === '\n') { masked += '\n'; return i + 2; }
if (src[i + 1] === '\r' && src[i + 2] === '\n') { masked += '\n'; return i + 3; }
masked += ' '; return i + 2;
};
const skipLineComment = (i, splice) => {
for (;;) {
while (i < n && src[i] !== '\n') i++;
if (splice) { let k = i - 1; if (src[k] === '\r') k--; if (k >= 0 && src[k] === '\\') { masked += '\n'; i++; continue; } }
return i;
}
};
const blankToClose = (j, close) => {
while (j < n && src.slice(j, j + close.length) !== close) { masked += src[j] === '\n' ? '\n' : ' '; j++; }
const end = Math.min(j + close.length, n);
blank(end - j);
return end;
};
const PERL_PAIR = { '(': ')', '{': '}', '[': ']', '<': '>' };
const scanPerlQuote = (i) => {
const m = src.slice(i, i + 3).match(/^(qq|qw|qr|qx|tr|q|m|s|y)(?![A-Za-z0-9_])/);
if (!m) return -1;
const op = m[1];
let j = i + op.length;
while (src[j] === ' ' || src[j] === '\t') j++;
const open = src[j];
if (!open || /[A-Za-z0-9_\s=,;)\]}>]/.test(open)) return -1;
const twoBody = op === 's' || op === 'tr' || op === 'y';
blank(j - i + 1); j++;
const scanBody = (o, cl) => {
let depth = 1;
while (j < n) {
const d = src[j];
if (d === '\\') { masked += ' '; masked += src[j + 1] === '\n' ? '\n' : ' '; j += 2; continue; }
if (PERL_PAIR[o] && d === o) depth++;
else if (d === cl) { depth--; if (depth === 0) { blank(1); j++; return; } }
masked += d === '\n' ? '\n' : ' '; j++;
}
};
scanBody(open, PERL_PAIR[open] ?? open);
if (twoBody) {
let o2 = open, c2 = PERL_PAIR[open] ?? open;
if (PERL_PAIR[open]) { while (src[j] === ' ' || src[j] === '\t') j++; o2 = src[j]; c2 = PERL_PAIR[o2] ?? o2; if (o2) { blank(1); j++; } }
scanBody(o2, c2);
}
return j;
};
const scanTemplate = (i) => {
masked += ' '; i++;
const stk = ['`'];
while (i < n && stk.length > 0) {
const d = src[i];
if (d === '\\') { i = emitEsc(i); continue; }
const top = stk[stk.length - 1];
if (top === '`') {
if (d === '`') { stk.pop(); masked += ' '; i++; continue; }
if (d === '$' && src[i + 1] === '{') { stk.push('{'); masked += ' '; i += 2; continue; }
} else if (top === '"' || top === "'") {
if (d === top) { stk.pop(); masked += ' '; i++; continue; }
if (d === '\n') { stk.pop(); masked += '\n'; i++; continue; }
} else { // ${…} interpolation — BLANK it (display text, not a bounds guard; and an interpolation
if (d === '}') { stk.pop(); masked += ' '; i++; continue; } // reached through a nested template
if (d === '{') { stk.push('{'); masked += ' '; i++; continue; } // must never leak — false-VERIFY)
if (d === '`') { stk.push('`'); masked += ' '; i++; continue; }
if (d === '"' || d === "'") { stk.push(d); masked += ' '; i++; continue; }
}
masked += d === '\n' ? '\n' : ' '; i++;
}
return i;
};
for (let i = 0; i < n; ) {
const c = src[i], c2 = src[i + 1];
if (t.podDoc && (i === 0 || src[i - 1] === '\n')) {
let le = i; while (le < n && src[le] !== '\n') le++;
// __END__/__DATA__ must be at column 0 to be a Perl trailer — test from the line's first char (not
// a trimmed copy) so an indented one is treated as ordinary code, not over-blanked to EOF.
if (/^__(?:END|DATA)__\b/.test(src.slice(i, le))) { while (i < n) { masked += src[i] === '\n' ? '\n' : ' '; i++; } continue; }
if (c === '=' && /[A-Za-z]/.test(c2) && /\n=cut\b/.test(src.slice(i))) {
for (;;) {
let j = i; while (j < n && src[j] !== '\n') j++;
const isCut = /^=cut\b/.test(src.slice(i, j));
blank(j - i); i = j;
if (i < n) { masked += '\n'; i++; }
if (isCut || i >= n) break;
}
continue;
}
}
if (t.perlQuote && (c === 'q' || c === 'm' || c === 's' || c === 'y' || c === 't') && !isIdent(src[i - 1])) {
const j = scanPerlQuote(i);
if (j >= 0) { i = j; continue; }
}
if (t.slashComment && c === '/' && c2 === '/') { i = skipLineComment(i, t.splice); continue; }
if (t.hashComment && c === '#') { i = skipLineComment(i, false); continue; }
if (t.dashComment && c === '-' && c2 === '-') { i = skipLineComment(i, false); continue; }
if (t.pctComment && c === '%') { i = skipLineComment(i, false); continue; }
if (t.blockComment && c === '/' && c2 === '*') {
i += 2; while (i < n && !(src[i] === '*' && src[i + 1] === '/')) { if (src[i] === '\n') masked += '\n'; i++; } i += 2; continue;
}
if (t.cppRaw && !isIdent(src[i - 1])) {
const m = src.slice(i, i + 4).match(/^(u8R|uR|UR|LR|R)"/);
if (m) {
let j = i + m[0].length, delim = '';
while (j < n && src[j] !== '(' && src[j] !== '\n' && src[j] !== ' ' && src[j] !== '\\' && delim.length <= 16) { delim += src[j]; j++; }
if (src[j] === '(') { blank(j - i + 1); i = blankToClose(j + 1, ')' + delim + '"'); continue; }
}
}
if (t.rustRaw && !isIdent(src[i - 1])) {
const m = src.slice(i, i + 260).match(/^b?r(#*)"/);
if (m) { blank(m[0].length); i = blankToClose(i + m[0].length, '"' + m[1]); continue; }
}
if (t.heredoc === 'php' && c === '<' && c2 === '<' && src[i + 2] === '<') { // PHP <<<WORD here/nowdoc
let j = i + 3;
while (src[j] === ' ' || src[j] === '\t') j++;
let q = ''; if (src[j] === "'" || src[j] === '"') { q = src[j]; j++; }
let w = ''; while (j < n && /[A-Za-z0-9_]/.test(src[j])) { w += src[j]; j++; }
if (q && src[j] === q) j++;
if (w) { blank(j - i); heredocs.push({ w, indent: true, php: true }); i = j; continue; }
}
if ((t.heredoc === 'shell' || t.heredoc === 'ruby') && c === '<' && c2 === '<' && src[i + 2] !== '<' && src[i + 2] !== '=' && src[i - 1] !== '<') {
let j = i + 2;
const squig = src[j] === '-' || src[j] === '~'; if (squig) j++;
if (t.heredoc === 'shell') while (src[j] === ' ' || src[j] === '\t') j++;
let q = ''; if (src[j] === "'" || src[j] === '"' || src[j] === '`') { q = src[j]; j++; }
let w = ''; while (j < n && /[A-Za-z0-9_]/.test(src[j])) { w += src[j]; j++; }
if (q && src[j] === q) j++;
const bareOk = w && !squig && !q && t.heredoc !== 'shell' && new RegExp('\\n[ \\t]*' + w + '[ \\t\\r]*(?:\\n|$)').test(src.slice(j));
if (w && (t.heredoc === 'shell' || squig || q || bareOk)) { blank(j - i); heredocs.push({ w, indent: squig || bareOk }); i = j; continue; }
}
if (t.dollarQuote && c === '$') {
let k = i + 1; while (k < n && /[A-Za-z0-9_]/.test(src[k])) k++;
if (src[k] === '$') { const tag = src.slice(i, k + 1); blank(tag.length); i = blankToClose(k + 1, tag); continue; }
}
if (t.pyTriple && (c === '"' || c === "'") && src[i + 1] === c && src[i + 2] === c) {
blank(3); let j = i + 3;
while (j < n && !(src[j] === c && src[j + 1] === c && src[j + 2] === c)) {
if (src[j] === '\\') { j = emitEsc(j); continue; }
masked += src[j] === '\n' ? '\n' : ' '; j++;
}
if (j < n) { blank(3); j += 3; }
i = j; continue;
}
if (t.template && c === '`') { i = scanTemplate(i); continue; }
if (t.rawBacktick && c === '`') {
masked += ' '; i++;
while (i < n && src[i] !== '`') { masked += src[i] === '\n' ? '\n' : ' '; i++; }
masked += ' '; if (src[i] === '`') i++;
continue;
}
if (c === '"' || c === "'") {
masked += ' '; i++;
while (i < n && src[i] !== c) {
if (src[i] === '\\') { i = emitEsc(i); continue; }
if (src[i] === '\n') break;
masked += ' '; i++;
}
masked += ' '; if (src[i] === c) i++;
continue;
}
if (c === '\n') {
masked += '\n'; i++;
while (heredocs.length > 0) {
const hd = heredocs.shift();
for (;;) {
let j = i; while (j < n && src[j] !== '\n') j++;
const stripped = src.slice(i, j).replace(/\r$/, '');
blank(j - i); i = j;
const isTerm = hd.php ? new RegExp('^[ \\t]*' + hd.w + '(?![A-Za-z0-9_])').test(stripped)
: (hd.indent ? stripped.trim() === hd.w : stripped === hd.w);
if (i < n) { masked += '\n'; i++; }
if (isTerm || i >= n) break;
}
}
continue;
}
masked += c; i++;
}
const lines = masked.split('\n');
if (!t.regexAware) return lines;
return lines.map((line) => {
const first = line.indexOf('/');
return first === -1 ? line : line.slice(0, first) + ' '.repeat(line.length - first);
});
}
function guardExistsInSource(g, repoRoot) {
if (!g || !g.file || !g.quote) return 'no-quote';
let src;
try { src = fs.readFileSync(path.join(repoRoot, g.file), 'utf8'); } catch { return 'file-missing'; }
const norm = (s) => String(s).replace(/\s+/g, '').toLowerCase();
if (norm(g.quote).length >= 6 && norm(src).includes(norm(g.quote))) return 'verified';
const cmp = String(g.quote).match(/([\w.()>_-]+)\s*(>=|<=|==|!=|>|<)\s*([\w.()>_-]+)/);
if (cmp) {
const a = norm(cmp[1]), b = norm(cmp[3]);
for (const line of src.split('\n')) { const nl = norm(line); if (a && b && nl.includes(a) && nl.includes(b)) return 'verified'; }
const coreToken = (s) => norm(s).replace(/^[($@%\s]+/, '').replace(/[)\s;,]+$/, '');
const CMP = '([\\w.()\\[\\]>_$@-]+)\\s*(>=|<=|==|!=|>|<)\\s*([\\w.()\\[\\]>_$@-]+)';
const GUARD_SHAPE = /(?:>=|<=|==|!=)|\b(?:if|else|return|break|continue|throw|goto)\b|\b(?:assert|clamp|min|max|bound|bounded|validate|sanitize|require|abort|reject|checked?|verify|ensure|limit)\s*\(/i;
// reduce to code-only lines (strip comments + BLANK string/template-literal contents), match PER
// line — so a guard that lives only in a comment or string can't false-verify, a `//` inside a
// string isn't mistaken for a comment, and a quote can't stitch across two unrelated lines.
const codeLines = codeLinesOf(src, langTraits(g.file));
const q = norm(g.quote);
if (q.length >= 6 && GUARD_SHAPE.test(g.quote) && codeLines.some((l) => norm(l).includes(q))) return 'verified';
const MIRROR = { '>': '<', '<': '>', '>=': '<=', '<=': '>=', '==': '==', '!=': '!=' };
const cited = String(g.quote).match(new RegExp(CMP));
if (cited) {
const a = coreToken(cited[1]), b = coreToken(cited[3]), op = cited[2];
if (a && b && a !== b) {
for (const line of codeLines) {
const re = new RegExp(CMP, 'g');
let m;
while ((m = re.exec(line)) !== null) {
const la = coreToken(m[1]), lb = coreToken(m[3]), lop = m[2];
// A source `<` whose right operand is immediately followed by `,`, `>`, or `<` opens a GENERIC
// argument list (`Map<K, V>`, `Vec<u8>`, `Nested<Vec<…>>`), not a comparison — skip it.
if (lop === '<') {
const nextCh = (line.slice(re.lastIndex).match(/^\s*(\S)/) ?? [])[1];
if (nextCh === ',' || nextCh === '>' || nextCh === '<') continue;
}
// operands AND operator must match — same order, or reversed with a mirrored operator.
// The operator is the whole meaning of a bounds guard: a cited `idx >= len` must not verify
// against a source `idx == len`.
if (la === a && lb === b && lop === op) return 'verified';
if (la === b && lb === a && lop === MIRROR[op]) return 'verified';
}
}
}
}
return 'hallucinated';
}
@@ -137,6 +385,20 @@ function selfTest() {
ok('parseVerdict extracts JSON', parseVerdict('blah {"verdict":"SURVIVED","killing_guard":null,"why":"none"} tail')?.verdict === 'SURVIVED');
ok('parseVerdict ignores prose braces, takes last verdict', parseVerdict('I see memcpy(&info, x) {note} reasoning...\n```json\n{"verdict":"REFUTED","killing_guard":{"file":"a.c","line":5,"quote":"if(n>cap)return"},"why":"bounded"}\n```')?.verdict === 'REFUTED');
ok('parseVerdict returns null on reasoning-only (no JSON)', parseVerdict('Let me trace the path: 1. UDP reception 2. routing — truncated') === null);
// cite-check mirror parity with src/mission/adjudicate.ts (regex-literal tokenizing + operator match).
// Each case fails under the OLD logic and passes under the fixed logic — pins the port faithfully.
const _tmp = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'refute-st-'));
const _fix = (name, body) => { fs.writeFileSync(path.join(_tmp, name), body); return name; };
const _gv = (file, quote) => guardExistsInSource({ file, quote }, _tmp);
ok('cite: guard after a quote-bearing regex still verifies',
_gv(_fix('a.js', "s = s.replace(/'/g, 'x');\nif (userId >= tenant.limit) throw e;\n"), 'userId >= tenant.limit') === 'verified');
ok('cite: a comparison only inside a regex is NOT verified',
_gv(_fix('b.js', 'const re = /idx >= len/; return arr[idx];\n'), 'idx >= len') === 'hallucinated');
ok('cite: wrong operator (cited >= vs source ==) is NOT verified',
_gv(_fix('c.c', 'if (idx == len) return -1;\nreturn arr[idx];\n'), 'idx >= len') === 'hallucinated');
ok('cite: reversed-order mirror (cited cap < n vs source n > cap) verifies',
_gv(_fix('d.c', 'if (n > cap) return;\n'), 'cap < n') === 'verified');
fs.rmSync(_tmp, { recursive: true, force: true });
console.log(`\n${fail === 0 ? '✅ ALL PASS' : `❌ ${fail} FAILED`} — ${pass}/${pass + fail}\n`);
process.exit(fail === 0 ? 0 : 1);
}