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,