@event4u/agent-config 16.1.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
import * as fs from 'node:fs';
|
|
56
56
|
import * as os from 'node:os';
|
|
57
57
|
import * as path from 'node:path';
|
|
58
|
+
|
|
59
|
+
import * as yaml from 'js-yaml';
|
|
58
60
|
import process from 'node:process';
|
|
59
61
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
60
62
|
|
|
@@ -88,12 +90,28 @@ export const CONFIGURED_OUTCOMES = ['refusal', 'halt-by-state', 'warning', 'unen
|
|
|
88
90
|
|
|
89
91
|
export type ConfiguredOutcome = (typeof CONFIGURED_OUTCOMES)[number];
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* What the `Answered` column prints for a pair the configuration never dated.
|
|
95
|
+
*
|
|
96
|
+
* A word rather than an empty cell, because an empty cell in a rendered table
|
|
97
|
+
* is indistinguishable from a rendering bug, and this one is the finding.
|
|
98
|
+
* `lint_hook_manifest._check_slot_answers` is what actually refuses it; this
|
|
99
|
+
* gate's job is to make sure the published document cannot hide it.
|
|
100
|
+
*/
|
|
101
|
+
export const UNDATED = '`undated`';
|
|
102
|
+
|
|
91
103
|
export interface MatrixRow {
|
|
92
104
|
host: string;
|
|
93
105
|
slot: string;
|
|
94
106
|
outcome: ConfiguredOutcome;
|
|
95
107
|
/** The configuration values the outcome was read off, quoted for the table. */
|
|
96
108
|
backing: string;
|
|
109
|
+
/**
|
|
110
|
+
* `answered_at` for this pair, or `undated` when the configuration carries
|
|
111
|
+
* none. Printed LAST so the outcome stays in column three and
|
|
112
|
+
* {@link committedCells} keeps reading the same index it always did.
|
|
113
|
+
*/
|
|
114
|
+
answered: string;
|
|
97
115
|
}
|
|
98
116
|
|
|
99
117
|
export interface BuildResult {
|
|
@@ -192,6 +210,7 @@ export function buildRows(lowering: HostLowering): BuildResult {
|
|
|
192
210
|
slot: reading.slot,
|
|
193
211
|
outcome,
|
|
194
212
|
backing: backingFor(reading, surface.fail_policy),
|
|
213
|
+
answered: surface.slots.get(reading.slot)?.answered_at ?? UNDATED,
|
|
195
214
|
});
|
|
196
215
|
}
|
|
197
216
|
}
|
|
@@ -295,10 +314,12 @@ export function renderRegion(lowering: HostLowering): string {
|
|
|
295
314
|
out.push('');
|
|
296
315
|
out.push(summaryLine(lowering, rows));
|
|
297
316
|
out.push('');
|
|
298
|
-
out.push('| Host | Slot | Configured outcome | Backing |');
|
|
299
|
-
out.push('
|
|
317
|
+
out.push('| Host | Slot | Configured outcome | Backing | Answered |');
|
|
318
|
+
out.push('|---|---|---|---|---|');
|
|
300
319
|
for (const r of rows) {
|
|
301
|
-
out.push(
|
|
320
|
+
out.push(
|
|
321
|
+
`| \`${r.host}\` | \`${r.slot}\` | \`${r.outcome}\` | ${r.backing} | ${r.answered} |`,
|
|
322
|
+
);
|
|
302
323
|
}
|
|
303
324
|
out.push('');
|
|
304
325
|
if (slotlessHosts.length === 0) {
|
|
@@ -394,6 +415,220 @@ function _read(root: string, rel: string): string | null {
|
|
|
394
415
|
/** Flags this gate understands. `--root` is the only one taking a value. */
|
|
395
416
|
const KNOWN_FLAGS: ReadonlySet<string> = new Set(['--self-test', '--root', '--write', '--quiet']);
|
|
396
417
|
|
|
418
|
+
/** Where the concern→slot bindings and per-concern severities live. */
|
|
419
|
+
export const MANIFEST_REL = path.join('src', 'scripts', 'hook_manifest.yaml');
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Why a bound concern's verdict cannot leave its slot.
|
|
423
|
+
*
|
|
424
|
+
* Three states, not two, and the third is the one an earlier version of this
|
|
425
|
+
* audit got wrong by skipping it. They are ordered worst-first:
|
|
426
|
+
*
|
|
427
|
+
* - `unlowerable` — the lowering table carries NO row for this slot on this
|
|
428
|
+
* host, so the installer writes no native binding at all.
|
|
429
|
+
* `cowork` is the worked case: `slots: {}` against sixty-odd
|
|
430
|
+
* declared bindings. Skipping it printed `0/0 · 0 blocking`,
|
|
431
|
+
* byte-identical to a host that binds nothing — a false
|
|
432
|
+
* green on the host with the largest gap.
|
|
433
|
+
* - `null-block` — a row exists and its literal `block_exit` is null. The
|
|
434
|
+
* slot is wired and a refusal has nowhere to go.
|
|
435
|
+
* - `stale-proof` — the literal `block_exit` CAN deny, but the row's
|
|
436
|
+
* `verified` block has expired, so `effective` reads null.
|
|
437
|
+
* This is a statement about this package's provenance, NOT
|
|
438
|
+
* about the host: `host_lowering.yaml` says in its own
|
|
439
|
+
* header that an absent `verified` "does NOT mean the host
|
|
440
|
+
* cannot enforce". Printing "cannot deny" here would
|
|
441
|
+
* assert exactly the host fact that file disclaims, so it
|
|
442
|
+
* is reported as its own class and worded differently.
|
|
443
|
+
*/
|
|
444
|
+
export type NoDenyReason = 'unlowerable' | 'null-block' | 'stale-proof';
|
|
445
|
+
|
|
446
|
+
/** One concern bound where its verdict cannot produce a refusal. */
|
|
447
|
+
export interface NullBlockBinding {
|
|
448
|
+
readonly host: string;
|
|
449
|
+
readonly slot: string;
|
|
450
|
+
readonly concern: string;
|
|
451
|
+
/** The concern's declared `severity`, or `(undeclared)`. */
|
|
452
|
+
readonly severity: string;
|
|
453
|
+
readonly reason: NoDenyReason;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** What {@link concernSlotAudit} establishes for one host. */
|
|
457
|
+
export interface HostConcernAudit {
|
|
458
|
+
readonly host: string;
|
|
459
|
+
/** Lowerable slots whose literal `block_exit` can deny. */
|
|
460
|
+
readonly denySlots: number;
|
|
461
|
+
/** Lowerable slots in total. */
|
|
462
|
+
readonly lowerableSlots: number;
|
|
463
|
+
/** Distinct slots this host binds a concern to, whether lowerable or not. */
|
|
464
|
+
readonly boundSlots: number;
|
|
465
|
+
/**
|
|
466
|
+
* Bindings whose verdict cannot become a refusal, worst reason first and
|
|
467
|
+
* `blocking` severity ahead of the rest within a reason.
|
|
468
|
+
*
|
|
469
|
+
* An `advisory` concern here is CONSISTENT, not a defect — most concerns are
|
|
470
|
+
* advisory and belong on a reporting slot. The row that matters is a
|
|
471
|
+
* `blocking` severity: a concern declared to refuse, bound where a refusal
|
|
472
|
+
* has nowhere to go.
|
|
473
|
+
*/
|
|
474
|
+
readonly noDeny: readonly NullBlockBinding[];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Slots that carry a dispatcher verdict. `ask` is a UI affordance, not one. */
|
|
478
|
+
const NON_VERDICT_SLOTS: ReadonlySet<string> = new Set(['ask']);
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Cross-reference the manifest's concern bindings against the lowering table's
|
|
482
|
+
* per-slot `block_exit`.
|
|
483
|
+
*
|
|
484
|
+
* road-to-a-content-scanner-on-a-slot-that-can-refuse 2.1. The gate above
|
|
485
|
+
* compares published host-SLOT rows against the configuration and is green
|
|
486
|
+
* whenever those agree — it never asks whether a CONCERN is bound to a slot
|
|
487
|
+
* that can carry its severity. That question was answered once, by an external
|
|
488
|
+
* reader comparing two files by hand, which is why it drifted back: a hand
|
|
489
|
+
* comparison leaves no number behind. This makes it a printed count.
|
|
490
|
+
*
|
|
491
|
+
* DELIBERATELY NOT A FAILURE. It prints; the exit code is unchanged. Failing
|
|
492
|
+
* would red the tree on bindings that predate this check — the same reason the
|
|
493
|
+
* continuity and estate ratchets report distance-to-target instead of gating on
|
|
494
|
+
* it. What a reader gets is a number that moves when a binding or a slot moves.
|
|
495
|
+
*
|
|
496
|
+
* NOTHING BOUND IS SILENTLY DROPPED. Every bound slot lands in exactly one of
|
|
497
|
+
* {can deny, one of the three {@link NoDenyReason} classes, non-verdict}, and
|
|
498
|
+
* `boundSlots` publishes the denominator so a reader can check the arithmetic
|
|
499
|
+
* rather than trust it. That is the correction the first version needed: it
|
|
500
|
+
* skipped any slot missing from the lowering table, which is precisely the
|
|
501
|
+
* largest gap in the tree.
|
|
502
|
+
*/
|
|
503
|
+
export function concernSlotAudit(root: string, lowering: HostLowering): HostConcernAudit[] {
|
|
504
|
+
const manifestText = _read(root, MANIFEST_REL);
|
|
505
|
+
if (manifestText === null) return [];
|
|
506
|
+
let manifest: unknown;
|
|
507
|
+
try {
|
|
508
|
+
manifest = yaml.load(manifestText);
|
|
509
|
+
} catch {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
if (manifest === null || typeof manifest !== 'object') return [];
|
|
513
|
+
const m = manifest as Record<string, unknown>;
|
|
514
|
+
|
|
515
|
+
const severities = new Map<string, string>();
|
|
516
|
+
const concerns = m['concerns'];
|
|
517
|
+
if (concerns !== null && typeof concerns === 'object') {
|
|
518
|
+
for (const [name, spec] of Object.entries(concerns as Record<string, unknown>)) {
|
|
519
|
+
const sev =
|
|
520
|
+
spec !== null && typeof spec === 'object'
|
|
521
|
+
? (spec as Record<string, unknown>)['severity']
|
|
522
|
+
: undefined;
|
|
523
|
+
severities.set(name, typeof sev === 'string' ? sev : '(undeclared)');
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const platforms = m['platforms'];
|
|
528
|
+
if (platforms === null || typeof platforms !== 'object') return [];
|
|
529
|
+
|
|
530
|
+
const out: HostConcernAudit[] = [];
|
|
531
|
+
for (const [host, blockRaw] of Object.entries(platforms as Record<string, unknown>)) {
|
|
532
|
+
const reading = readSlots(lowering, host);
|
|
533
|
+
const rows = new Map<string, SlotReading>(reading.slots.map((s: SlotReading) => [s.slot, s]));
|
|
534
|
+
const denySlots = reading.slots.filter((s: SlotReading) => s.literal !== null).length;
|
|
535
|
+
|
|
536
|
+
const noDeny: NullBlockBinding[] = [];
|
|
537
|
+
let boundSlots = 0;
|
|
538
|
+
if (blockRaw !== null && typeof blockRaw === 'object') {
|
|
539
|
+
for (const [slot, value] of Object.entries(blockRaw as Record<string, unknown>)) {
|
|
540
|
+
if (!Array.isArray(value)) continue;
|
|
541
|
+
if (NON_VERDICT_SLOTS.has(slot)) continue;
|
|
542
|
+
boundSlots += 1;
|
|
543
|
+
const row = rows.get(slot);
|
|
544
|
+
// The literal decides whether a refusal can leave the slot; the
|
|
545
|
+
// `verified` currency decides only whether this package has
|
|
546
|
+
// cited its proof. Reading `effective` here would turn an
|
|
547
|
+
// expired citation into a claim that the host cannot enforce.
|
|
548
|
+
const reason: NoDenyReason | null =
|
|
549
|
+
row === undefined
|
|
550
|
+
? 'unlowerable'
|
|
551
|
+
: row.literal === null
|
|
552
|
+
? 'null-block'
|
|
553
|
+
: row.effective === null
|
|
554
|
+
? 'stale-proof'
|
|
555
|
+
: null;
|
|
556
|
+
if (reason === null) continue;
|
|
557
|
+
for (const concern of value) {
|
|
558
|
+
if (typeof concern !== 'string') continue;
|
|
559
|
+
noDeny.push({
|
|
560
|
+
host,
|
|
561
|
+
slot,
|
|
562
|
+
concern,
|
|
563
|
+
severity: severities.get(concern) ?? '(unknown concern)',
|
|
564
|
+
reason,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const reasonRank = (r: NoDenyReason): number =>
|
|
570
|
+
r === 'unlowerable' ? 0 : r === 'null-block' ? 1 : 2;
|
|
571
|
+
const sevRank = (s: string): number => (s === 'blocking' ? 0 : s === 'advisory' ? 2 : 1);
|
|
572
|
+
noDeny.sort(
|
|
573
|
+
(a, b) =>
|
|
574
|
+
reasonRank(a.reason) - reasonRank(b.reason) ||
|
|
575
|
+
sevRank(a.severity) - sevRank(b.severity) ||
|
|
576
|
+
a.slot.localeCompare(b.slot) ||
|
|
577
|
+
a.concern.localeCompare(b.concern),
|
|
578
|
+
);
|
|
579
|
+
out.push({
|
|
580
|
+
host,
|
|
581
|
+
denySlots,
|
|
582
|
+
lowerableSlots: reading.slots.length,
|
|
583
|
+
boundSlots,
|
|
584
|
+
noDeny,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** How each {@link NoDenyReason} is worded. `stale-proof` never says "cannot". */
|
|
591
|
+
const REASON_TEXT: Readonly<Record<NoDenyReason, string>> = {
|
|
592
|
+
unlowerable: 'no lowering row for this slot — nothing is bound natively at all',
|
|
593
|
+
'null-block': 'block_exit is null — a refusal has nowhere to go',
|
|
594
|
+
'stale-proof': 'block_exit can deny, but this row’s `verified` proof has expired',
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
/** Render the audit as the lines the reporter prints. */
|
|
598
|
+
export function renderConcernAudit(audit: readonly HostConcernAudit[]): string[] {
|
|
599
|
+
const lines: string[] = [];
|
|
600
|
+
lines.push('');
|
|
601
|
+
lines.push(`${GATE} · concerns bound where a verdict cannot become a refusal`);
|
|
602
|
+
lines.push(
|
|
603
|
+
' A blocking severity here is a concern declared to refuse, bound where a refusal',
|
|
604
|
+
);
|
|
605
|
+
lines.push(' cannot happen. An advisory one is consistent and is counted, not flagged.');
|
|
606
|
+
lines.push(' `stale-proof` is a statement about this package’s citation, never about the host.');
|
|
607
|
+
for (const h of audit) {
|
|
608
|
+
const blocking = h.noDeny.filter((b) => b.severity === 'blocking');
|
|
609
|
+
const others = h.noDeny.length - blocking.length;
|
|
610
|
+
lines.push(
|
|
611
|
+
` ${h.host.padEnd(10)} ${String(h.denySlots)}/${String(h.lowerableSlots)} lowerable slot(s) can deny · ` +
|
|
612
|
+
`${String(h.boundSlots)} verdict-bearing slot(s) bound · ` +
|
|
613
|
+
`${String(blocking.length)} blocking + ${String(others)} non-blocking binding(s) cannot refuse`,
|
|
614
|
+
);
|
|
615
|
+
// One line per (reason, slot) rather than per concern: a host with an
|
|
616
|
+
// empty `slots:` map has sixty-odd bindings and the reason is the fact,
|
|
617
|
+
// not the roster.
|
|
618
|
+
const seen = new Set<string>();
|
|
619
|
+
for (const b of h.noDeny) {
|
|
620
|
+
if (b.severity !== 'blocking') continue;
|
|
621
|
+
const key = `${b.reason}/${b.slot}`;
|
|
622
|
+
if (!seen.has(key)) {
|
|
623
|
+
seen.add(key);
|
|
624
|
+
lines.push(` ⚠️ ${b.slot} — ${REASON_TEXT[b.reason]}`);
|
|
625
|
+
}
|
|
626
|
+
lines.push(` ${b.concern} (${b.severity})`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return lines;
|
|
630
|
+
}
|
|
631
|
+
|
|
397
632
|
export function main(argv?: readonly string[]): number {
|
|
398
633
|
const args = argv ?? process.argv.slice(2);
|
|
399
634
|
// An unrecognised flag is refused rather than ignored. Both ratification
|
|
@@ -517,6 +752,13 @@ export function main(argv?: readonly string[]): number {
|
|
|
517
752
|
});
|
|
518
753
|
ledger.report();
|
|
519
754
|
|
|
755
|
+
// The concern-vs-slot audit prints on every read-only run, before the
|
|
756
|
+
// verdict, so it is visible whether the row comparison passed or failed.
|
|
757
|
+
// It never changes the exit code — see `concernSlotAudit`.
|
|
758
|
+
for (const line of renderConcernAudit(concernSlotAudit(root, lowering))) {
|
|
759
|
+
process.stdout.write(`${line}\n`);
|
|
760
|
+
}
|
|
761
|
+
|
|
520
762
|
if (findings.length > 0) {
|
|
521
763
|
for (const f of findings) process.stdout.write(`❌ ${GATE}: ${f}\n`);
|
|
522
764
|
process.stdout.write(
|
|
@@ -95,6 +95,12 @@ export interface LedgerFinding {
|
|
|
95
95
|
rationale?: string;
|
|
96
96
|
verified_by?: string;
|
|
97
97
|
date?: string;
|
|
98
|
+
/**
|
|
99
|
+
* The tree's mechanical disproof of the finding's premise, written by
|
|
100
|
+
* `self_review_gate.annotateContradicted`. A finding carrying one is not
|
|
101
|
+
* blocking — see `isBlocking`.
|
|
102
|
+
*/
|
|
103
|
+
contradicted?: string;
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
export interface Ledger {
|
|
@@ -220,8 +226,21 @@ function _clean_review_reason(artifact: Record<string, unknown>): string {
|
|
|
220
226
|
);
|
|
221
227
|
}
|
|
222
228
|
|
|
223
|
-
/**
|
|
224
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Mirrors self_review_gate.classifyBlocking — security/claim × critical/high,
|
|
231
|
+
* MINUS anything the tree disproved.
|
|
232
|
+
*
|
|
233
|
+
* The `contradicted` clause is not a second policy; it is the same one, and
|
|
234
|
+
* the mirror has to carry it or the two gates disagree: the review would stop
|
|
235
|
+
* counting a disproved deletion claim as blocking while the release kept
|
|
236
|
+
* demanding a disposition for it. A `contradicted` value is written only by
|
|
237
|
+
* `annotateContradicted`, which checks the claim against
|
|
238
|
+
* `git diff --name-status base...HEAD` AND the working tree.
|
|
239
|
+
*/
|
|
240
|
+
export function isBlocking(f: Pick<LedgerFinding, 'kind' | 'severity' | 'contradicted'>): boolean {
|
|
241
|
+
if ((f.contradicted ?? '').trim() !== '') {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
225
244
|
return (
|
|
226
245
|
(f.kind === 'security' || f.kind === 'claim') &&
|
|
227
246
|
(f.severity === 'critical' || f.severity === 'high')
|
|
@@ -413,7 +432,7 @@ export function resolve_release_status(release: string): ReleaseStatus {
|
|
|
413
432
|
|
|
414
433
|
/** Findings the gate comment reported but the ledger never ingested. */
|
|
415
434
|
export function unrecorded_findings(
|
|
416
|
-
reported: ReadonlyArray<Pick<LedgerFinding, 'finding_id' | 'severity' | 'kind' | 'title'>>,
|
|
435
|
+
reported: ReadonlyArray<Pick<LedgerFinding, 'finding_id' | 'severity' | 'kind' | 'title' | 'contradicted'>>,
|
|
417
436
|
ledger: readonly LedgerFinding[],
|
|
418
437
|
): string[] {
|
|
419
438
|
const known = new Set(ledger.map((f) => f.finding_id));
|
|
@@ -476,10 +476,81 @@ const HOOK_MANIFEST_TRIPLE: Triple = {
|
|
|
476
476
|
},
|
|
477
477
|
};
|
|
478
478
|
|
|
479
|
+
const HOST_LOWERING_YAML = 'src/scripts/hooks/host_lowering.yaml';
|
|
480
|
+
const HOST_LOWERING_JSON = 'src/scripts/hooks/host_lowering.json';
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* The second compiled plumbing table, and it is here for the reason the first
|
|
484
|
+
* one is: the fingerprint hashes the YAML TEXT, so a comment-only edit to
|
|
485
|
+
* `host_lowering.yaml` invalidates the committed JSON while every parsed field
|
|
486
|
+
* stays byte-identical. That file is mostly comments — 16.8 kB of source
|
|
487
|
+
* compiling to 5.9 kB of data — so the comment-only case is the LIKELY one
|
|
488
|
+
* here, not the corner case.
|
|
489
|
+
*
|
|
490
|
+
* A stale sibling is slow, never wrong: `loadHostLowering` falls through to the
|
|
491
|
+
* YAML source on a fingerprint mismatch. So what this row protects is the
|
|
492
|
+
* measured 9 ms per dispatch, which would otherwise be given back silently and
|
|
493
|
+
* would surface only as the hook-latency gate flapping again.
|
|
494
|
+
*/
|
|
495
|
+
const HOST_LOWERING_TRIPLE: Triple = {
|
|
496
|
+
id: 'host-lowering-compiled',
|
|
497
|
+
output: HOST_LOWERING_JSON,
|
|
498
|
+
remedy: './scripts-run src/scripts/compile_hook_manifest --table host-lowering',
|
|
499
|
+
why:
|
|
500
|
+
'the compiled lowering table carries a fingerprint hashed over the YAML TEXT, so even a ' +
|
|
501
|
+
'comment-only edit to the source invalidates it — and no `task` target regenerates it',
|
|
502
|
+
sourcesOf() {
|
|
503
|
+
return { ok: true, sources: [{ kind: 'file', value: HOST_LOWERING_YAML }] };
|
|
504
|
+
},
|
|
505
|
+
regenerate(root, workDir) {
|
|
506
|
+
const outFile = path.join(workDir, 'host_lowering.json');
|
|
507
|
+
const tsx = path.join(
|
|
508
|
+
root,
|
|
509
|
+
'node_modules',
|
|
510
|
+
'.bin',
|
|
511
|
+
process.platform === 'win32' ? 'tsx.cmd' : 'tsx',
|
|
512
|
+
);
|
|
513
|
+
if (!fs.existsSync(tsx)) {
|
|
514
|
+
return {
|
|
515
|
+
ok: false,
|
|
516
|
+
reason: `no tsx at ${path.relative(root, tsx)} — run \`npm ci\` before this gate`,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const res = spawnSync(
|
|
520
|
+
tsx,
|
|
521
|
+
[
|
|
522
|
+
path.join(root, 'src', 'scripts', 'compile_hook_manifest.ts'),
|
|
523
|
+
'--table',
|
|
524
|
+
'host-lowering',
|
|
525
|
+
'--out',
|
|
526
|
+
outFile,
|
|
527
|
+
],
|
|
528
|
+
{ cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 },
|
|
529
|
+
);
|
|
530
|
+
if (res.status !== 0) {
|
|
531
|
+
return {
|
|
532
|
+
ok: false,
|
|
533
|
+
reason:
|
|
534
|
+
`compile_hook_manifest --table host-lowering exited ${String(res.status)}: ` +
|
|
535
|
+
`${(res.stderr ?? '').trim()}`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
return { ok: true, text: fs.readFileSync(outFile, 'utf8') };
|
|
540
|
+
} catch (e) {
|
|
541
|
+
return {
|
|
542
|
+
ok: false,
|
|
543
|
+
reason: `compile_hook_manifest reported success but wrote no readable file (${String(e)})`,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
|
|
479
549
|
export const REGISTRY: readonly Triple[] = [
|
|
480
550
|
CENSUS_TRIPLE,
|
|
481
551
|
INSTALL_BUNDLE_TRIPLE,
|
|
482
552
|
HOOK_MANIFEST_TRIPLE,
|
|
553
|
+
HOST_LOWERING_TRIPLE,
|
|
483
554
|
];
|
|
484
555
|
|
|
485
556
|
// ---------------------------------------------------------------------------
|