@descryy/core 0.1.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/LICENSE +21 -0
- package/dist/capabilities/index.d.ts +3 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +2 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/preconditions.d.ts +99 -0
- package/dist/capabilities/preconditions.d.ts.map +1 -0
- package/dist/capabilities/preconditions.js +94 -0
- package/dist/capabilities/preconditions.js.map +1 -0
- package/dist/contracts/engine.d.ts +90 -0
- package/dist/contracts/engine.d.ts.map +1 -0
- package/dist/contracts/engine.js +291 -0
- package/dist/contracts/engine.js.map +1 -0
- package/dist/contracts/index.d.ts +9 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/orphans.d.ts +96 -0
- package/dist/contracts/orphans.d.ts.map +1 -0
- package/dist/contracts/orphans.js +241 -0
- package/dist/contracts/orphans.js.map +1 -0
- package/dist/contracts/paths.d.ts +132 -0
- package/dist/contracts/paths.d.ts.map +1 -0
- package/dist/contracts/paths.js +245 -0
- package/dist/contracts/paths.js.map +1 -0
- package/dist/contracts/shapes.d.ts +99 -0
- package/dist/contracts/shapes.d.ts.map +1 -0
- package/dist/contracts/shapes.js +196 -0
- package/dist/contracts/shapes.js.map +1 -0
- package/dist/governance/budget.d.ts +89 -0
- package/dist/governance/budget.d.ts.map +1 -0
- package/dist/governance/budget.js +101 -0
- package/dist/governance/budget.js.map +1 -0
- package/dist/governance/candidate-boundary.d.ts +49 -0
- package/dist/governance/candidate-boundary.d.ts.map +1 -0
- package/dist/governance/candidate-boundary.js +51 -0
- package/dist/governance/candidate-boundary.js.map +1 -0
- package/dist/governance/escalation.d.ts +63 -0
- package/dist/governance/escalation.d.ts.map +1 -0
- package/dist/governance/escalation.js +72 -0
- package/dist/governance/escalation.js.map +1 -0
- package/dist/governance/fact-boundary.d.ts +77 -0
- package/dist/governance/fact-boundary.d.ts.map +1 -0
- package/dist/governance/fact-boundary.js +91 -0
- package/dist/governance/fact-boundary.js.map +1 -0
- package/dist/governance/finding-funnel.d.ts +109 -0
- package/dist/governance/finding-funnel.d.ts.map +1 -0
- package/dist/governance/finding-funnel.js +137 -0
- package/dist/governance/finding-funnel.js.map +1 -0
- package/dist/governance/index.d.ts +11 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +6 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/graph/build.d.ts +127 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +0 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/cross-language.d.ts +70 -0
- package/dist/graph/cross-language.d.ts.map +1 -0
- package/dist/graph/cross-language.js +104 -0
- package/dist/graph/cross-language.js.map +1 -0
- package/dist/graph/index.d.ts +11 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +6 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/merge.d.ts +95 -0
- package/dist/graph/merge.d.ts.map +1 -0
- package/dist/graph/merge.js +131 -0
- package/dist/graph/merge.js.map +1 -0
- package/dist/graph/persist.d.ts +42 -0
- package/dist/graph/persist.d.ts.map +1 -0
- package/dist/graph/persist.js +52 -0
- package/dist/graph/persist.js.map +1 -0
- package/dist/graph/runtime-confirmation.d.ts +169 -0
- package/dist/graph/runtime-confirmation.d.ts.map +1 -0
- package/dist/graph/runtime-confirmation.js +318 -0
- package/dist/graph/runtime-confirmation.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger/index.d.ts +5 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +3 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/ledger/mint.d.ts +81 -0
- package/dist/ledger/mint.d.ts.map +1 -0
- package/dist/ledger/mint.js +138 -0
- package/dist/ledger/mint.js.map +1 -0
- package/dist/ledger/resolve.d.ts +124 -0
- package/dist/ledger/resolve.d.ts.map +1 -0
- package/dist/ledger/resolve.js +290 -0
- package/dist/ledger/resolve.js.map +1 -0
- package/dist/multipr/fingerprint.d.ts +56 -0
- package/dist/multipr/fingerprint.d.ts.map +1 -0
- package/dist/multipr/fingerprint.js +169 -0
- package/dist/multipr/fingerprint.js.map +1 -0
- package/dist/multipr/hidden-dependency.d.ts +120 -0
- package/dist/multipr/hidden-dependency.d.ts.map +1 -0
- package/dist/multipr/hidden-dependency.js +185 -0
- package/dist/multipr/hidden-dependency.js.map +1 -0
- package/dist/multipr/index.d.ts +14 -0
- package/dist/multipr/index.d.ts.map +1 -0
- package/dist/multipr/index.js +8 -0
- package/dist/multipr/index.js.map +1 -0
- package/dist/multipr/mechanical.d.ts +239 -0
- package/dist/multipr/mechanical.d.ts.map +1 -0
- package/dist/multipr/mechanical.js +333 -0
- package/dist/multipr/mechanical.js.map +1 -0
- package/dist/multipr/migration-heads.d.ts +160 -0
- package/dist/multipr/migration-heads.d.ts.map +1 -0
- package/dist/multipr/migration-heads.js +212 -0
- package/dist/multipr/migration-heads.js.map +1 -0
- package/dist/multipr/overlap.d.ts +186 -0
- package/dist/multipr/overlap.d.ts.map +1 -0
- package/dist/multipr/overlap.js +254 -0
- package/dist/multipr/overlap.js.map +1 -0
- package/dist/multipr/scope-store.d.ts +105 -0
- package/dist/multipr/scope-store.d.ts.map +1 -0
- package/dist/multipr/scope-store.js +182 -0
- package/dist/multipr/scope-store.js.map +1 -0
- package/dist/multipr/superseded.d.ts +133 -0
- package/dist/multipr/superseded.d.ts.map +1 -0
- package/dist/multipr/superseded.js +128 -0
- package/dist/multipr/superseded.js.map +1 -0
- package/dist/query/confirmed-facts.d.ts +56 -0
- package/dist/query/confirmed-facts.d.ts.map +1 -0
- package/dist/query/confirmed-facts.js +64 -0
- package/dist/query/confirmed-facts.js.map +1 -0
- package/dist/query/declared-value-closure.d.ts +126 -0
- package/dist/query/declared-value-closure.d.ts.map +1 -0
- package/dist/query/declared-value-closure.js +156 -0
- package/dist/query/declared-value-closure.js.map +1 -0
- package/dist/query/index.d.ts +28 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +15 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/memory.d.ts +20 -0
- package/dist/query/memory.d.ts.map +1 -0
- package/dist/query/memory.js +55 -0
- package/dist/query/memory.js.map +1 -0
- package/dist/query/provider.d.ts +127 -0
- package/dist/query/provider.d.ts.map +1 -0
- package/dist/query/provider.js +54 -0
- package/dist/query/provider.js.map +1 -0
- package/dist/query/queries.d.ts +119 -0
- package/dist/query/queries.d.ts.map +1 -0
- package/dist/query/queries.js +267 -0
- package/dist/query/queries.js.map +1 -0
- package/dist/query/refusal-fetch.d.ts +114 -0
- package/dist/query/refusal-fetch.d.ts.map +1 -0
- package/dist/query/refusal-fetch.js +200 -0
- package/dist/query/refusal-fetch.js.map +1 -0
- package/dist/query/refusal-questions.d.ts +123 -0
- package/dist/query/refusal-questions.d.ts.map +1 -0
- package/dist/query/refusal-questions.js +122 -0
- package/dist/query/refusal-questions.js.map +1 -0
- package/dist/query/root-cause-score.d.ts +218 -0
- package/dist/query/root-cause-score.d.ts.map +1 -0
- package/dist/query/root-cause-score.js +251 -0
- package/dist/query/root-cause-score.js.map +1 -0
- package/dist/query/row-closure-picture.d.ts +108 -0
- package/dist/query/row-closure-picture.d.ts.map +1 -0
- package/dist/query/row-closure-picture.js +131 -0
- package/dist/query/row-closure-picture.js.map +1 -0
- package/dist/query/similar-incidents.d.ts +76 -0
- package/dist/query/similar-incidents.d.ts.map +1 -0
- package/dist/query/similar-incidents.js +169 -0
- package/dist/query/similar-incidents.js.map +1 -0
- package/dist/query/sqlite.d.ts +13 -0
- package/dist/query/sqlite.d.ts.map +1 -0
- package/dist/query/sqlite.js +54 -0
- package/dist/query/sqlite.js.map +1 -0
- package/dist/query/traverse.d.ts +110 -0
- package/dist/query/traverse.d.ts.map +1 -0
- package/dist/query/traverse.js +0 -0
- package/dist/query/traverse.js.map +1 -0
- package/dist/query/unresolved.d.ts +195 -0
- package/dist/query/unresolved.d.ts.map +1 -0
- package/dist/query/unresolved.js +225 -0
- package/dist/query/unresolved.js.map +1 -0
- package/dist/query/verification-status.d.ts +134 -0
- package/dist/query/verification-status.d.ts.map +1 -0
- package/dist/query/verification-status.js +163 -0
- package/dist/query/verification-status.js.map +1 -0
- package/dist/recording/index.d.ts +26 -0
- package/dist/recording/index.d.ts.map +1 -0
- package/dist/recording/index.js +30 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording/migrate.d.ts +22 -0
- package/dist/recording/migrate.d.ts.map +1 -0
- package/dist/recording/migrate.js +51 -0
- package/dist/recording/migrate.js.map +1 -0
- package/dist/recording/reader.d.ts +27 -0
- package/dist/recording/reader.d.ts.map +1 -0
- package/dist/recording/reader.js +55 -0
- package/dist/recording/reader.js.map +1 -0
- package/dist/recording/redact.d.ts +63 -0
- package/dist/recording/redact.d.ts.map +1 -0
- package/dist/recording/redact.js +98 -0
- package/dist/recording/redact.js.map +1 -0
- package/dist/recording/schema.d.ts +41 -0
- package/dist/recording/schema.d.ts.map +1 -0
- package/dist/recording/schema.js +104 -0
- package/dist/recording/schema.js.map +1 -0
- package/dist/recording/writer.d.ts +120 -0
- package/dist/recording/writer.d.ts.map +1 -0
- package/dist/recording/writer.js +81 -0
- package/dist/recording/writer.js.map +1 -0
- package/dist/scoping/fanout.d.ts +86 -0
- package/dist/scoping/fanout.d.ts.map +1 -0
- package/dist/scoping/fanout.js +95 -0
- package/dist/scoping/fanout.js.map +1 -0
- package/dist/scoping/index.d.ts +10 -0
- package/dist/scoping/index.d.ts.map +1 -0
- package/dist/scoping/index.js +6 -0
- package/dist/scoping/index.js.map +1 -0
- package/dist/scoping/score.d.ts +83 -0
- package/dist/scoping/score.d.ts.map +1 -0
- package/dist/scoping/score.js +125 -0
- package/dist/scoping/score.js.map +1 -0
- package/dist/scoping/tiers.d.ts +32 -0
- package/dist/scoping/tiers.d.ts.map +1 -0
- package/dist/scoping/tiers.js +35 -0
- package/dist/scoping/tiers.js.map +1 -0
- package/dist/scoping/traverse.d.ts +60 -0
- package/dist/scoping/traverse.d.ts.map +1 -0
- package/dist/scoping/traverse.js +172 -0
- package/dist/scoping/traverse.js.map +1 -0
- package/dist/scoping/weights.d.ts +97 -0
- package/dist/scoping/weights.d.ts.map +1 -0
- package/dist/scoping/weights.js +167 -0
- package/dist/scoping/weights.js.map +1 -0
- package/dist/sources/git/diff.d.ts +53 -0
- package/dist/sources/git/diff.d.ts.map +1 -0
- package/dist/sources/git/diff.js +140 -0
- package/dist/sources/git/diff.js.map +1 -0
- package/dist/sources/git/env.d.ts +2 -0
- package/dist/sources/git/env.d.ts.map +1 -0
- package/dist/sources/git/env.js +21 -0
- package/dist/sources/git/env.js.map +1 -0
- package/dist/sources/git/history.d.ts +140 -0
- package/dist/sources/git/history.d.ts.map +1 -0
- package/dist/sources/git/history.js +314 -0
- package/dist/sources/git/history.js.map +1 -0
- package/dist/sources/git/index.d.ts +7 -0
- package/dist/sources/git/index.d.ts.map +1 -0
- package/dist/sources/git/index.js +4 -0
- package/dist/sources/git/index.js.map +1 -0
- package/dist/sources/git/source.d.ts +95 -0
- package/dist/sources/git/source.d.ts.map +1 -0
- package/dist/sources/git/source.js +0 -0
- package/dist/sources/git/source.js.map +1 -0
- package/dist/sources/migrations/dialects.d.ts +59 -0
- package/dist/sources/migrations/dialects.d.ts.map +1 -0
- package/dist/sources/migrations/dialects.js +84 -0
- package/dist/sources/migrations/dialects.js.map +1 -0
- package/dist/sources/migrations/index.d.ts +3 -0
- package/dist/sources/migrations/index.d.ts.map +1 -0
- package/dist/sources/migrations/index.js +2 -0
- package/dist/sources/migrations/index.js.map +1 -0
- package/dist/sources/migrations/read.d.ts +113 -0
- package/dist/sources/migrations/read.d.ts.map +1 -0
- package/dist/sources/migrations/read.js +270 -0
- package/dist/sources/migrations/read.js.map +1 -0
- package/dist/sources/workspace/index.d.ts +3 -0
- package/dist/sources/workspace/index.d.ts.map +1 -0
- package/dist/sources/workspace/index.js +2 -0
- package/dist/sources/workspace/index.js.map +1 -0
- package/dist/sources/workspace/workspace.d.ts +64 -0
- package/dist/sources/workspace/workspace.d.ts.map +1 -0
- package/dist/sources/workspace/workspace.js +294 -0
- package/dist/sources/workspace/workspace.js.map +1 -0
- package/dist/store/driver/driver.d.ts +82 -0
- package/dist/store/driver/driver.d.ts.map +1 -0
- package/dist/store/driver/driver.js +31 -0
- package/dist/store/driver/driver.js.map +1 -0
- package/dist/store/driver/node-sqlite.d.ts +28 -0
- package/dist/store/driver/node-sqlite.d.ts.map +1 -0
- package/dist/store/driver/node-sqlite.js +188 -0
- package/dist/store/driver/node-sqlite.js.map +1 -0
- package/dist/store/index.d.ts +33 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +46 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/migrate.d.ts +68 -0
- package/dist/store/migrate.d.ts.map +1 -0
- package/dist/store/migrate.js +140 -0
- package/dist/store/migrate.js.map +1 -0
- package/dist/store/reader.d.ts +127 -0
- package/dist/store/reader.d.ts.map +1 -0
- package/dist/store/reader.js +269 -0
- package/dist/store/reader.js.map +1 -0
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +322 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/writer.d.ts +116 -0
- package/dist/store/writer.d.ts.map +1 -0
- package/dist/store/writer.js +279 -0
- package/dist/store/writer.js.map +1 -0
- package/dist/tiers/certify.d.ts +375 -0
- package/dist/tiers/certify.d.ts.map +1 -0
- package/dist/tiers/certify.js +434 -0
- package/dist/tiers/certify.js.map +1 -0
- package/dist/tiers/index.d.ts +5 -0
- package/dist/tiers/index.d.ts.map +1 -0
- package/dist/tiers/index.js +3 -0
- package/dist/tiers/index.js.map +1 -0
- package/dist/tiers/ladder.d.ts +73 -0
- package/dist/tiers/ladder.d.ts.map +1 -0
- package/dist/tiers/ladder.js +197 -0
- package/dist/tiers/ladder.js.map +1 -0
- package/dist/validation/config-graph.d.ts +104 -0
- package/dist/validation/config-graph.d.ts.map +1 -0
- package/dist/validation/config-graph.js +205 -0
- package/dist/validation/config-graph.js.map +1 -0
- package/dist/validation/env.d.ts +243 -0
- package/dist/validation/env.d.ts.map +1 -0
- package/dist/validation/env.js +447 -0
- package/dist/validation/env.js.map +1 -0
- package/dist/validation/index.d.ts +16 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/join-substitution.d.ts +186 -0
- package/dist/validation/join-substitution.d.ts.map +1 -0
- package/dist/validation/join-substitution.js +191 -0
- package/dist/validation/join-substitution.js.map +1 -0
- package/dist/validation/migrations.d.ts +133 -0
- package/dist/validation/migrations.d.ts.map +1 -0
- package/dist/validation/migrations.js +315 -0
- package/dist/validation/migrations.js.map +1 -0
- package/dist/validation/nodes.d.ts +32 -0
- package/dist/validation/nodes.d.ts.map +1 -0
- package/dist/validation/nodes.js +45 -0
- package/dist/validation/nodes.js.map +1 -0
- package/dist/validation/rollback.d.ts +124 -0
- package/dist/validation/rollback.d.ts.map +1 -0
- package/dist/validation/rollback.js +302 -0
- package/dist/validation/rollback.js.map +1 -0
- package/dist/validation/route-drift.d.ts +108 -0
- package/dist/validation/route-drift.d.ts.map +1 -0
- package/dist/validation/route-drift.js +103 -0
- package/dist/validation/route-drift.js.map +1 -0
- package/dist/validation/sources.d.ts +153 -0
- package/dist/validation/sources.d.ts.map +1 -0
- package/dist/validation/sources.js +409 -0
- package/dist/validation/sources.js.map +1 -0
- package/dist/worker/index.d.ts +5 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +3 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/pool.d.ts +73 -0
- package/dist/worker/pool.d.ts.map +1 -0
- package/dist/worker/pool.js +203 -0
- package/dist/worker/pool.js.map +1 -0
- package/dist/worker/protocol.d.ts +91 -0
- package/dist/worker/protocol.d.ts.map +1 -0
- package/dist/worker/protocol.js +19 -0
- package/dist/worker/protocol.js.map +1 -0
- package/dist/worker/traversal.worker.d.ts +25 -0
- package/dist/worker/traversal.worker.d.ts.map +1 -0
- package/dist/worker/traversal.worker.js +124 -0
- package/dist/worker/traversal.worker.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hard per-run budget — §41 rule 16, `ai-layer-plan.md` §11 (Phase 4,
|
|
3
|
+
* P17/P18). `ai-governance-spec.md` §8 names this explicitly as *not* its
|
|
4
|
+
* document's job: *"a property of the harness (Phase 4, P17/P18), not of a
|
|
5
|
+
* per-finding gate... Full spec: Phase 4 (P17), not this document."* This
|
|
6
|
+
* module is that property.
|
|
7
|
+
*
|
|
8
|
+
* *"Enforced by the runtime harness, not by the AI's own judgment of when to
|
|
9
|
+
* stop"* (`descry-ai-layer.md`, "Execution budget"). A cap the model can
|
|
10
|
+
* reason its way past is not a cap (§41 rule 16's own words) — so, same as
|
|
11
|
+
* G1–G4, this is a pure function the pipeline calls, never a prompt
|
|
12
|
+
* instruction.
|
|
13
|
+
*
|
|
14
|
+
* ## Four caps, two scopes
|
|
15
|
+
*
|
|
16
|
+
* `ai-layer-plan.md` §11 names four: *"max turns per investigation · max
|
|
17
|
+
* cost per run · max concurrent open hypotheses · max tool calls per
|
|
18
|
+
* hypothesis."* Two are per-run (cost, concurrency); two are per-hypothesis
|
|
19
|
+
* (turns, tool calls) and are checked against every currently-open
|
|
20
|
+
* hypothesis independently — one hypothesis running long does not budget-fail
|
|
21
|
+
* a sibling that has spent nothing.
|
|
22
|
+
*
|
|
23
|
+
* ## The default values
|
|
24
|
+
*
|
|
25
|
+
* `documents/architecture/investigation-budget-spec.md` (P17) is where these
|
|
26
|
+
* are ruled and justified; this module imports them from there in spirit —
|
|
27
|
+
* they are declared once, here, because the spec document is prose and this
|
|
28
|
+
* is the value the harness actually reads. A caller may always supply its
|
|
29
|
+
* own {@link InvestigationBudget}; the exported default is a starting point,
|
|
30
|
+
* tuned from data, the same convention `CLAUDE.md` already applies to node
|
|
31
|
+
* budgets.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* P17's ruled starting values — `documents/architecture/investigation-budget-spec.md`.
|
|
35
|
+
* Not a measurement; a starting point to tune from real runs, same as the
|
|
36
|
+
* node budgets `CLAUDE.md` already treats this way.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_INVESTIGATION_BUDGET = {
|
|
39
|
+
maxTurnsPerInvestigation: 8,
|
|
40
|
+
maxToolCallsPerHypothesis: 6,
|
|
41
|
+
maxConcurrentOpenHypotheses: 5,
|
|
42
|
+
costCeilingPerRunUsd: 2.0,
|
|
43
|
+
};
|
|
44
|
+
export const BUDGET_CAP_NAMES = [
|
|
45
|
+
"maxTurnsPerInvestigation",
|
|
46
|
+
"maxToolCallsPerHypothesis",
|
|
47
|
+
"maxConcurrentOpenHypotheses",
|
|
48
|
+
"costCeilingPerRunUsd",
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Check `usage` against `budget`. Never throws — this is a check, not an
|
|
52
|
+
* execution of the thing it is checking, same posture as G1–G4.
|
|
53
|
+
*/
|
|
54
|
+
export function checkRunBudget(budget, usage) {
|
|
55
|
+
const exceeded = [];
|
|
56
|
+
if (usage.costSpentUsd > budget.costCeilingPerRunUsd) {
|
|
57
|
+
exceeded.push({
|
|
58
|
+
cap: "costCeilingPerRunUsd",
|
|
59
|
+
limit: budget.costCeilingPerRunUsd,
|
|
60
|
+
actual: usage.costSpentUsd,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (usage.openHypotheses.length > budget.maxConcurrentOpenHypotheses) {
|
|
64
|
+
exceeded.push({
|
|
65
|
+
cap: "maxConcurrentOpenHypotheses",
|
|
66
|
+
limit: budget.maxConcurrentOpenHypotheses,
|
|
67
|
+
actual: usage.openHypotheses.length,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
for (const hypothesis of usage.openHypotheses) {
|
|
71
|
+
if (hypothesis.turnsSpent > budget.maxTurnsPerInvestigation) {
|
|
72
|
+
exceeded.push({
|
|
73
|
+
cap: "maxTurnsPerInvestigation",
|
|
74
|
+
limit: budget.maxTurnsPerInvestigation,
|
|
75
|
+
actual: hypothesis.turnsSpent,
|
|
76
|
+
hypothesisId: hypothesis.hypothesisId,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (hypothesis.toolCallsSpent > budget.maxToolCallsPerHypothesis) {
|
|
80
|
+
exceeded.push({
|
|
81
|
+
cap: "maxToolCallsPerHypothesis",
|
|
82
|
+
limit: budget.maxToolCallsPerHypothesis,
|
|
83
|
+
actual: hypothesis.toolCallsSpent,
|
|
84
|
+
hypothesisId: hypothesis.hypothesisId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { withinBudget: exceeded.length === 0, exceeded };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether a specific hypothesis has reached its per-hypothesis round limit —
|
|
92
|
+
* the input `../../ir/src/hypothesis.ts`'s `deriveHypothesisState` needs for
|
|
93
|
+
* its `roundLimitReached` field. A thin, named convenience over
|
|
94
|
+
* {@link checkRunBudget} so a caller does not have to re-derive "does this
|
|
95
|
+
* one hypothesis's own cap show up in `exceeded`" by hand.
|
|
96
|
+
*/
|
|
97
|
+
export function hypothesisRoundLimitReached(budget, usage) {
|
|
98
|
+
return (usage.turnsSpent > budget.maxTurnsPerInvestigation ||
|
|
99
|
+
usage.toolCallsSpent > budget.maxToolCallsPerHypothesis);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/governance/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAcH;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAwB;IAC/D,wBAAwB,EAAE,CAAC;IAC3B,yBAAyB,EAAE,CAAC;IAC5B,2BAA2B,EAAE,CAAC;IAC9B,oBAAoB,EAAE,GAAG;CAC1B,CAAC;AAgBF,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,0BAA0B;IAC1B,2BAA2B;IAC3B,6BAA6B;IAC7B,sBAAsB;CACd,CAAC;AAkBX;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAA2B,EAAE,KAAqB;IAC/E,MAAM,QAAQ,GAAwB,EAAE,CAAC;IAEzC,IAAI,KAAK,CAAC,YAAY,GAAG,MAAM,CAAC,oBAAoB,EAAE,CAAC;QACrD,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,EAAE,sBAAsB;YAC3B,KAAK,EAAE,MAAM,CAAC,oBAAoB;YAClC,MAAM,EAAE,KAAK,CAAC,YAAY;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,2BAA2B,EAAE,CAAC;QACrE,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,EAAE,6BAA6B;YAClC,KAAK,EAAE,MAAM,CAAC,2BAA2B;YACzC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM;SACpC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QAC9C,IAAI,UAAU,CAAC,UAAU,GAAG,MAAM,CAAC,wBAAwB,EAAE,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC;gBACZ,GAAG,EAAE,0BAA0B;gBAC/B,KAAK,EAAE,MAAM,CAAC,wBAAwB;gBACtC,MAAM,EAAE,UAAU,CAAC,UAAU;gBAC7B,YAAY,EAAE,UAAU,CAAC,YAAY;aACtC,CAAC,CAAC;QACL,CAAC;QACD,IAAI,UAAU,CAAC,cAAc,GAAG,MAAM,CAAC,yBAAyB,EAAE,CAAC;YACjE,QAAQ,CAAC,IAAI,CAAC;gBACZ,GAAG,EAAE,2BAA2B;gBAChC,KAAK,EAAE,MAAM,CAAC,yBAAyB;gBACvC,MAAM,EAAE,UAAU,CAAC,cAAc;gBACjC,YAAY,EAAE,UAAU,CAAC,YAAY;aACtC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAA2B,EAC3B,KAA4B;IAE5B,OAAO,CACL,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,wBAAwB;QAClD,KAAK,CAAC,cAAc,GAAG,MAAM,CAAC,yBAAyB,CACxD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G2 — the candidate boundary (`ai-governance-spec.md` §3, `ai-layer-plan.md` §5).
|
|
3
|
+
*
|
|
4
|
+
* **The plan (stage 6) may only contain items from the deterministic
|
|
5
|
+
* candidate set stages 3–5 produced.** Stage 5 (risk prioritization) is
|
|
6
|
+
* itself deterministic — "scores in, ordering out. Does not invent
|
|
7
|
+
* candidates" (`ai-layer-plan.md` §1) — so a plan item naming a node outside
|
|
8
|
+
* that set could only have been invented by stage 6's own model slot, and is
|
|
9
|
+
* rejected outright rather than down-ranked.
|
|
10
|
+
*
|
|
11
|
+
* Same shape as G1, one stage later, and built as the same kind of pure
|
|
12
|
+
* function: `ai-governance-spec.md` §10 — "candidate-set membership checking
|
|
13
|
+
* on the plan... no new tool surface, no new IR type." A candidate set is a
|
|
14
|
+
* set of node ids; this module does not care how stages 3–5 produced it.
|
|
15
|
+
*
|
|
16
|
+
* ## Reject the item, not the plan
|
|
17
|
+
*
|
|
18
|
+
* `ai-governance-spec.md` §3's own negative control: "a plan naming a target
|
|
19
|
+
* outside the candidate set. G2 must reject the item, not the whole plan —
|
|
20
|
+
* the rest of the plan proceeds if the remaining items are valid." So the
|
|
21
|
+
* result partitions the plan rather than failing it wholesale.
|
|
22
|
+
*
|
|
23
|
+
* `PlanItem` is deliberately the smallest shape G2 needs — one node id.
|
|
24
|
+
* Phase 3's real plan item will carry more (a rationale, a priority, a test
|
|
25
|
+
* strategy); the generic parameter lets a caller pass its real type through
|
|
26
|
+
* unchanged and get it back in `accepted`/`rejected`, rather than this
|
|
27
|
+
* module owning a plan-item type the pipeline would then have to conform to.
|
|
28
|
+
*/
|
|
29
|
+
/** The smallest shape G2 needs from a plan item: what node it targets. */
|
|
30
|
+
export interface PlanItem {
|
|
31
|
+
readonly targetNodeId: string;
|
|
32
|
+
}
|
|
33
|
+
export interface RejectedPlanItem<T extends PlanItem> {
|
|
34
|
+
readonly item: T;
|
|
35
|
+
/** Plain-English, ready to append to the slot's re-run prompt verbatim. */
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
}
|
|
38
|
+
export interface CandidateBoundaryResult<T extends PlanItem> {
|
|
39
|
+
readonly accepted: readonly T[];
|
|
40
|
+
readonly rejected: readonly RejectedPlanItem<T>[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check every item in `plan` against `candidateNodeIds` — the deterministic
|
|
44
|
+
* set stages 3–5 produced. An item whose `targetNodeId` is not in that set
|
|
45
|
+
* is rejected with a reason; the rest of the plan is unaffected. Never
|
|
46
|
+
* throws — this is a check, not an execution of the thing it is checking.
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkCandidateBoundary<T extends PlanItem>(plan: readonly T[], candidateNodeIds: ReadonlySet<string>): CandidateBoundaryResult<T>;
|
|
49
|
+
//# sourceMappingURL=candidate-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidate-boundary.d.ts","sourceRoot":"","sources":["../../src/governance/candidate-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,QAAQ;IAClD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,QAAQ;IACzD,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;CACnD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,QAAQ,EACvD,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,GACpC,uBAAuB,CAAC,CAAC,CAAC,CAgB5B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G2 — the candidate boundary (`ai-governance-spec.md` §3, `ai-layer-plan.md` §5).
|
|
3
|
+
*
|
|
4
|
+
* **The plan (stage 6) may only contain items from the deterministic
|
|
5
|
+
* candidate set stages 3–5 produced.** Stage 5 (risk prioritization) is
|
|
6
|
+
* itself deterministic — "scores in, ordering out. Does not invent
|
|
7
|
+
* candidates" (`ai-layer-plan.md` §1) — so a plan item naming a node outside
|
|
8
|
+
* that set could only have been invented by stage 6's own model slot, and is
|
|
9
|
+
* rejected outright rather than down-ranked.
|
|
10
|
+
*
|
|
11
|
+
* Same shape as G1, one stage later, and built as the same kind of pure
|
|
12
|
+
* function: `ai-governance-spec.md` §10 — "candidate-set membership checking
|
|
13
|
+
* on the plan... no new tool surface, no new IR type." A candidate set is a
|
|
14
|
+
* set of node ids; this module does not care how stages 3–5 produced it.
|
|
15
|
+
*
|
|
16
|
+
* ## Reject the item, not the plan
|
|
17
|
+
*
|
|
18
|
+
* `ai-governance-spec.md` §3's own negative control: "a plan naming a target
|
|
19
|
+
* outside the candidate set. G2 must reject the item, not the whole plan —
|
|
20
|
+
* the rest of the plan proceeds if the remaining items are valid." So the
|
|
21
|
+
* result partitions the plan rather than failing it wholesale.
|
|
22
|
+
*
|
|
23
|
+
* `PlanItem` is deliberately the smallest shape G2 needs — one node id.
|
|
24
|
+
* Phase 3's real plan item will carry more (a rationale, a priority, a test
|
|
25
|
+
* strategy); the generic parameter lets a caller pass its real type through
|
|
26
|
+
* unchanged and get it back in `accepted`/`rejected`, rather than this
|
|
27
|
+
* module owning a plan-item type the pipeline would then have to conform to.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Check every item in `plan` against `candidateNodeIds` — the deterministic
|
|
31
|
+
* set stages 3–5 produced. An item whose `targetNodeId` is not in that set
|
|
32
|
+
* is rejected with a reason; the rest of the plan is unaffected. Never
|
|
33
|
+
* throws — this is a check, not an execution of the thing it is checking.
|
|
34
|
+
*/
|
|
35
|
+
export function checkCandidateBoundary(plan, candidateNodeIds) {
|
|
36
|
+
const accepted = [];
|
|
37
|
+
const rejected = [];
|
|
38
|
+
for (const item of plan) {
|
|
39
|
+
if (candidateNodeIds.has(item.targetNodeId)) {
|
|
40
|
+
accepted.push(item);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
rejected.push({
|
|
44
|
+
item,
|
|
45
|
+
reason: `target node ${item.targetNodeId} is not in the candidate set stages 3-5 produced`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { accepted, rejected };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=candidate-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidate-boundary.js","sourceRoot":"","sources":["../../src/governance/candidate-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAkBH;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAkB,EAClB,gBAAqC;IAErC,MAAM,QAAQ,GAAQ,EAAE,CAAC;IACzB,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI;gBACJ,MAAM,EAAE,eAAe,IAAI,CAAC,YAAY,kDAAkD;aAC3F,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalation — §41 rule 17, `ai-layer-plan.md` §11 (Phase 4, P17/P18).
|
|
3
|
+
*
|
|
4
|
+
* *"AI must escalate to the developer rather than keep investigating
|
|
5
|
+
* when..."* — three conditions named in the architecture (budget exhausted,
|
|
6
|
+
* a hypothesis stuck past its round limit, unresolved graph/runtime
|
|
7
|
+
* contradiction), widened to five in the plan's own §11: *"budget exhausted
|
|
8
|
+
* with hypotheses open · unresolved contradictory evidence · a fact only a
|
|
9
|
+
* human holds · an action gate reached · every hypothesis refuted with the
|
|
10
|
+
* anomaly unexplained."*
|
|
11
|
+
*
|
|
12
|
+
* Same shape as {@link "./budget.ts"} and G1–G4: a pure function over
|
|
13
|
+
* caller-supplied facts, evaluated outside the model, never a prompt telling
|
|
14
|
+
* the model when to stop. `ai-layer-plan.md` §0's own words, one layer up:
|
|
15
|
+
* *"a rule the model can reason its way past is not a rule."*
|
|
16
|
+
*
|
|
17
|
+
* ## Four of five inputs are not computed here
|
|
18
|
+
*
|
|
19
|
+
* Only the budget trigger is derivable from `./budget.ts`'s own output.
|
|
20
|
+
* The other four name conditions this module has no way to detect on its
|
|
21
|
+
* own, and deliberately does not try to:
|
|
22
|
+
*
|
|
23
|
+
* - **Unresolved contradictory evidence** — §10's graph-vs-runtime
|
|
24
|
+
* contradiction machinery (P21) is what detects a contradiction at all;
|
|
25
|
+
* this module only reacts to the caller having found one.
|
|
26
|
+
* - **A fact only a human holds** — inherits the `questions` MCP tool /
|
|
27
|
+
* `ConfirmedFact` machinery (`packages/core/src/query/refusal-questions.ts`,
|
|
28
|
+
* `confirmed-facts.ts`) already built for a different stage. Whether an
|
|
29
|
+
* open, unanswered question exists is that machinery's own state, not
|
|
30
|
+
* something recomputed here.
|
|
31
|
+
* - **An action gate reached** — G5 (`ai-tool-contract.md` §7's two-call
|
|
32
|
+
* handshake) already governs whether a write may proceed; this module
|
|
33
|
+
* just names the fact of having reached it as one more reason to stop and
|
|
34
|
+
* hand control back.
|
|
35
|
+
* - **Every hypothesis refuted, anomaly unexplained** — depends on every
|
|
36
|
+
* open hypothesis's terminal state (`../../ir/src/hypothesis.ts`) plus a
|
|
37
|
+
* judgement that no other explanation covers the original anomaly. The
|
|
38
|
+
* "no other explanation" half is not a fact this module can derive from a
|
|
39
|
+
* list of rejected hypotheses alone, so it stays a caller-supplied flag.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ESCALATION_TRIGGERS: readonly ["budget-exhausted-with-hypotheses-open", "unresolved-contradictory-evidence", "fact-only-a-human-holds", "action-gate-reached", "every-hypothesis-refuted-unexplained"];
|
|
42
|
+
export type EscalationTrigger = (typeof ESCALATION_TRIGGERS)[number];
|
|
43
|
+
export interface EscalationCheckInput {
|
|
44
|
+
/** From `checkRunBudget(...).withinBudget === false` (`./budget.ts`). */
|
|
45
|
+
readonly budgetExceeded: boolean;
|
|
46
|
+
/** How many hypotheses are currently `investigating` or `supported` — not `candidate`, not terminal. */
|
|
47
|
+
readonly openHypothesesCount: number;
|
|
48
|
+
/** Caller-supplied: §10's contradiction machinery (P21) found a graph/runtime disagreement it could not resolve within the round limit. */
|
|
49
|
+
readonly hasUnresolvedContradiction: boolean;
|
|
50
|
+
/** Caller-supplied: the `questions`/`ConfirmedFact` machinery has an open, unanswered question this investigation needs to proceed. */
|
|
51
|
+
readonly hasUnansweredHumanQuestion: boolean;
|
|
52
|
+
/** Caller-supplied: G5 (the action gate) has been reached and needs developer approval before any write. */
|
|
53
|
+
readonly actionGateReached: boolean;
|
|
54
|
+
/** Caller-supplied: every hypothesis this run opened has reached `rejected`, and the anomaly that started the investigation still has no confirmed or supported explanation. */
|
|
55
|
+
readonly allHypothesesRejectedAnomalyUnexplained: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check `input` against all five triggers. Never throws — a check, not an
|
|
59
|
+
* execution of the thing it is checking. More than one trigger may fire in
|
|
60
|
+
* the same call; an empty result means the loop may keep running.
|
|
61
|
+
*/
|
|
62
|
+
export declare function checkEscalation(input: EscalationCheckInput): readonly EscalationTrigger[];
|
|
63
|
+
//# sourceMappingURL=escalation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation.d.ts","sourceRoot":"","sources":["../../src/governance/escalation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,eAAO,MAAM,mBAAmB,mLAMtB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,wGAAwG;IACxG,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,2IAA2I;IAC3I,QAAQ,CAAC,0BAA0B,EAAE,OAAO,CAAC;IAC7C,uIAAuI;IACvI,QAAQ,CAAC,0BAA0B,EAAE,OAAO,CAAC;IAC7C,4GAA4G;IAC5G,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,gLAAgL;IAChL,QAAQ,CAAC,uCAAuC,EAAE,OAAO,CAAC;CAC3D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,SAAS,iBAAiB,EAAE,CAoBzF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalation — §41 rule 17, `ai-layer-plan.md` §11 (Phase 4, P17/P18).
|
|
3
|
+
*
|
|
4
|
+
* *"AI must escalate to the developer rather than keep investigating
|
|
5
|
+
* when..."* — three conditions named in the architecture (budget exhausted,
|
|
6
|
+
* a hypothesis stuck past its round limit, unresolved graph/runtime
|
|
7
|
+
* contradiction), widened to five in the plan's own §11: *"budget exhausted
|
|
8
|
+
* with hypotheses open · unresolved contradictory evidence · a fact only a
|
|
9
|
+
* human holds · an action gate reached · every hypothesis refuted with the
|
|
10
|
+
* anomaly unexplained."*
|
|
11
|
+
*
|
|
12
|
+
* Same shape as {@link "./budget.ts"} and G1–G4: a pure function over
|
|
13
|
+
* caller-supplied facts, evaluated outside the model, never a prompt telling
|
|
14
|
+
* the model when to stop. `ai-layer-plan.md` §0's own words, one layer up:
|
|
15
|
+
* *"a rule the model can reason its way past is not a rule."*
|
|
16
|
+
*
|
|
17
|
+
* ## Four of five inputs are not computed here
|
|
18
|
+
*
|
|
19
|
+
* Only the budget trigger is derivable from `./budget.ts`'s own output.
|
|
20
|
+
* The other four name conditions this module has no way to detect on its
|
|
21
|
+
* own, and deliberately does not try to:
|
|
22
|
+
*
|
|
23
|
+
* - **Unresolved contradictory evidence** — §10's graph-vs-runtime
|
|
24
|
+
* contradiction machinery (P21) is what detects a contradiction at all;
|
|
25
|
+
* this module only reacts to the caller having found one.
|
|
26
|
+
* - **A fact only a human holds** — inherits the `questions` MCP tool /
|
|
27
|
+
* `ConfirmedFact` machinery (`packages/core/src/query/refusal-questions.ts`,
|
|
28
|
+
* `confirmed-facts.ts`) already built for a different stage. Whether an
|
|
29
|
+
* open, unanswered question exists is that machinery's own state, not
|
|
30
|
+
* something recomputed here.
|
|
31
|
+
* - **An action gate reached** — G5 (`ai-tool-contract.md` §7's two-call
|
|
32
|
+
* handshake) already governs whether a write may proceed; this module
|
|
33
|
+
* just names the fact of having reached it as one more reason to stop and
|
|
34
|
+
* hand control back.
|
|
35
|
+
* - **Every hypothesis refuted, anomaly unexplained** — depends on every
|
|
36
|
+
* open hypothesis's terminal state (`../../ir/src/hypothesis.ts`) plus a
|
|
37
|
+
* judgement that no other explanation covers the original anomaly. The
|
|
38
|
+
* "no other explanation" half is not a fact this module can derive from a
|
|
39
|
+
* list of rejected hypotheses alone, so it stays a caller-supplied flag.
|
|
40
|
+
*/
|
|
41
|
+
export const ESCALATION_TRIGGERS = [
|
|
42
|
+
"budget-exhausted-with-hypotheses-open",
|
|
43
|
+
"unresolved-contradictory-evidence",
|
|
44
|
+
"fact-only-a-human-holds",
|
|
45
|
+
"action-gate-reached",
|
|
46
|
+
"every-hypothesis-refuted-unexplained",
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Check `input` against all five triggers. Never throws — a check, not an
|
|
50
|
+
* execution of the thing it is checking. More than one trigger may fire in
|
|
51
|
+
* the same call; an empty result means the loop may keep running.
|
|
52
|
+
*/
|
|
53
|
+
export function checkEscalation(input) {
|
|
54
|
+
const triggers = [];
|
|
55
|
+
if (input.budgetExceeded && input.openHypothesesCount > 0) {
|
|
56
|
+
triggers.push("budget-exhausted-with-hypotheses-open");
|
|
57
|
+
}
|
|
58
|
+
if (input.hasUnresolvedContradiction) {
|
|
59
|
+
triggers.push("unresolved-contradictory-evidence");
|
|
60
|
+
}
|
|
61
|
+
if (input.hasUnansweredHumanQuestion) {
|
|
62
|
+
triggers.push("fact-only-a-human-holds");
|
|
63
|
+
}
|
|
64
|
+
if (input.actionGateReached) {
|
|
65
|
+
triggers.push("action-gate-reached");
|
|
66
|
+
}
|
|
67
|
+
if (input.allHypothesesRejectedAnomalyUnexplained) {
|
|
68
|
+
triggers.push("every-hypothesis-refuted-unexplained");
|
|
69
|
+
}
|
|
70
|
+
return triggers;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=escalation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation.js","sourceRoot":"","sources":["../../src/governance/escalation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,uCAAuC;IACvC,mCAAmC;IACnC,yBAAyB;IACzB,qBAAqB;IACrB,sCAAsC;CAC9B,CAAC;AAmBX;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,QAAQ,GAAwB,EAAE,CAAC;IAEzC,IAAI,KAAK,CAAC,cAAc,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,EAAE,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,KAAK,CAAC,0BAA0B,EAAE,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,KAAK,CAAC,0BAA0B,EAAE,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,KAAK,CAAC,uCAAuC,EAAE,CAAC;QAClD,QAAQ,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G1 — the fact boundary (`ai-governance-spec.md` §2, `ai-layer-plan.md` §5).
|
|
3
|
+
*
|
|
4
|
+
* **No fact enters the model's context unless a tool produced it. No fact
|
|
5
|
+
* leaves a model slot unless it references something a tool returned.**
|
|
6
|
+
*
|
|
7
|
+
* This is the first of the two gates P9 builds (`ai-governance-spec.md` §10):
|
|
8
|
+
* "citation-checking on every slot output... a pure function over
|
|
9
|
+
* already-recorded tool calls... no new tool surface, no new IR type."
|
|
10
|
+
*
|
|
11
|
+
* ## Why a citation is `{ seq }`, not `{ runId, seq }`
|
|
12
|
+
*
|
|
13
|
+
* `recorded_calls`'s primary key is `(run_id, seq)` (`../recording/schema.ts`),
|
|
14
|
+
* and the gate is defined as checking a claim "against **this run's own**
|
|
15
|
+
* recorded tool calls" — the run is the boundary, not a parameter a slot
|
|
16
|
+
* output gets to name. `checkFactBoundary` takes the `RecordedRun` it is
|
|
17
|
+
* checking against and a citation carries only `seq`. A citation naming a
|
|
18
|
+
* call from a different run does not fail because the run id mismatches —
|
|
19
|
+
* it fails because no call with that `seq` exists in *this* run's own
|
|
20
|
+
* ordered list, which is the same failure as a fabricated seq and needs no
|
|
21
|
+
* separate case (negative control 7, `ai-layer-plan.md` §9 layer 5).
|
|
22
|
+
*
|
|
23
|
+
* ## Why a whole claim is rejected on any unresolved citation
|
|
24
|
+
*
|
|
25
|
+
* A claim with two citations, one real and one fabricated, is not "half
|
|
26
|
+
* true." §41.1 (never invent graph facts) does not have a partial-credit
|
|
27
|
+
* form, so one bad citation invalidates the claim it was attached to, not
|
|
28
|
+
* just itself — the same posture `ai-tool-contract.md`'s invariants take
|
|
29
|
+
* toward a payload that "looks complete."
|
|
30
|
+
*
|
|
31
|
+
* ## Path is a key array, not a string
|
|
32
|
+
*
|
|
33
|
+
* `data.callers[0]` is a plausible citation shape, but parsing that string
|
|
34
|
+
* back into a traversal is a small grammar this gate does not need to own.
|
|
35
|
+
* `readonly (string | number)[]` says the same thing with no parser and no
|
|
36
|
+
* ambiguity about what `[0]` means against an object vs. an array.
|
|
37
|
+
*/
|
|
38
|
+
import type { RecordedRun } from "../recording/reader.ts";
|
|
39
|
+
/** One segment of a citation path: an object key or an array index. */
|
|
40
|
+
export type CitationPathSegment = string | number;
|
|
41
|
+
/**
|
|
42
|
+
* A pointer at one fact a tool call actually returned, within the run being
|
|
43
|
+
* checked. `seq` names the call (`recorded_calls.seq`); `path`, when given,
|
|
44
|
+
* narrows the citation to a field within that call's `result` rather than
|
|
45
|
+
* the whole envelope. Omitted `path` cites the whole result.
|
|
46
|
+
*/
|
|
47
|
+
export interface Citation {
|
|
48
|
+
readonly seq: number;
|
|
49
|
+
readonly path?: readonly CitationPathSegment[];
|
|
50
|
+
}
|
|
51
|
+
/** One factual assertion a model slot's output makes. */
|
|
52
|
+
export interface FactualClaim {
|
|
53
|
+
readonly text: string;
|
|
54
|
+
/** Empty means uncited — rejected outright, never treated as an implicit whole-run citation. */
|
|
55
|
+
readonly citations: readonly Citation[];
|
|
56
|
+
}
|
|
57
|
+
/** What a slot output looks like from G1's point of view — nothing else about it matters here. */
|
|
58
|
+
export interface SlotOutput {
|
|
59
|
+
readonly claims: readonly FactualClaim[];
|
|
60
|
+
}
|
|
61
|
+
export interface RejectedClaim {
|
|
62
|
+
readonly claim: FactualClaim;
|
|
63
|
+
/** Plain-English, one per failure — ready to append to the slot's re-run prompt verbatim. */
|
|
64
|
+
readonly reasons: readonly string[];
|
|
65
|
+
}
|
|
66
|
+
export interface FactBoundaryResult {
|
|
67
|
+
readonly accepted: readonly FactualClaim[];
|
|
68
|
+
readonly rejected: readonly RejectedClaim[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check every claim in `output` against `run`'s own recorded calls. A claim
|
|
72
|
+
* with no citations, or with any citation that does not resolve, is
|
|
73
|
+
* rejected with its reasons; everything else is accepted. Never throws —
|
|
74
|
+
* this is a check, not an execution of the thing it is checking.
|
|
75
|
+
*/
|
|
76
|
+
export declare function checkFactBoundary(output: SlotOutput, run: RecordedRun): FactBoundaryResult;
|
|
77
|
+
//# sourceMappingURL=fact-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fact-boundary.d.ts","sourceRoot":"","sources":["../../src/governance/fact-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAExE,uEAAuE;AACvE,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAChD;AAED,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;CACzC;AAED,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AA2BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,GAAG,kBAAkB,CAwB1F"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G1 — the fact boundary (`ai-governance-spec.md` §2, `ai-layer-plan.md` §5).
|
|
3
|
+
*
|
|
4
|
+
* **No fact enters the model's context unless a tool produced it. No fact
|
|
5
|
+
* leaves a model slot unless it references something a tool returned.**
|
|
6
|
+
*
|
|
7
|
+
* This is the first of the two gates P9 builds (`ai-governance-spec.md` §10):
|
|
8
|
+
* "citation-checking on every slot output... a pure function over
|
|
9
|
+
* already-recorded tool calls... no new tool surface, no new IR type."
|
|
10
|
+
*
|
|
11
|
+
* ## Why a citation is `{ seq }`, not `{ runId, seq }`
|
|
12
|
+
*
|
|
13
|
+
* `recorded_calls`'s primary key is `(run_id, seq)` (`../recording/schema.ts`),
|
|
14
|
+
* and the gate is defined as checking a claim "against **this run's own**
|
|
15
|
+
* recorded tool calls" — the run is the boundary, not a parameter a slot
|
|
16
|
+
* output gets to name. `checkFactBoundary` takes the `RecordedRun` it is
|
|
17
|
+
* checking against and a citation carries only `seq`. A citation naming a
|
|
18
|
+
* call from a different run does not fail because the run id mismatches —
|
|
19
|
+
* it fails because no call with that `seq` exists in *this* run's own
|
|
20
|
+
* ordered list, which is the same failure as a fabricated seq and needs no
|
|
21
|
+
* separate case (negative control 7, `ai-layer-plan.md` §9 layer 5).
|
|
22
|
+
*
|
|
23
|
+
* ## Why a whole claim is rejected on any unresolved citation
|
|
24
|
+
*
|
|
25
|
+
* A claim with two citations, one real and one fabricated, is not "half
|
|
26
|
+
* true." §41.1 (never invent graph facts) does not have a partial-credit
|
|
27
|
+
* form, so one bad citation invalidates the claim it was attached to, not
|
|
28
|
+
* just itself — the same posture `ai-tool-contract.md`'s invariants take
|
|
29
|
+
* toward a payload that "looks complete."
|
|
30
|
+
*
|
|
31
|
+
* ## Path is a key array, not a string
|
|
32
|
+
*
|
|
33
|
+
* `data.callers[0]` is a plausible citation shape, but parsing that string
|
|
34
|
+
* back into a traversal is a small grammar this gate does not need to own.
|
|
35
|
+
* `readonly (string | number)[]` says the same thing with no parser and no
|
|
36
|
+
* ambiguity about what `[0]` means against an object vs. an array.
|
|
37
|
+
*/
|
|
38
|
+
function resolvesWithin(value, path) {
|
|
39
|
+
let cursor = value;
|
|
40
|
+
for (const key of path) {
|
|
41
|
+
if (cursor === null || typeof cursor !== "object")
|
|
42
|
+
return false;
|
|
43
|
+
if (Array.isArray(cursor)) {
|
|
44
|
+
if (typeof key !== "number" || !Number.isInteger(key) || key < 0 || key >= cursor.length)
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
else if (!Object.hasOwn(cursor, key)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
cursor = cursor[key];
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function checkCitation(citation, callsBySeq) {
|
|
55
|
+
const call = callsBySeq.get(citation.seq);
|
|
56
|
+
if (call === undefined) {
|
|
57
|
+
return `citation seq ${citation.seq} does not match any tool call recorded in this run`;
|
|
58
|
+
}
|
|
59
|
+
if (citation.path !== undefined && !resolvesWithin(call.result, citation.path)) {
|
|
60
|
+
return `citation seq ${citation.seq} path ${JSON.stringify(citation.path)} does not resolve within that call's result`;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check every claim in `output` against `run`'s own recorded calls. A claim
|
|
66
|
+
* with no citations, or with any citation that does not resolve, is
|
|
67
|
+
* rejected with its reasons; everything else is accepted. Never throws —
|
|
68
|
+
* this is a check, not an execution of the thing it is checking.
|
|
69
|
+
*/
|
|
70
|
+
export function checkFactBoundary(output, run) {
|
|
71
|
+
const callsBySeq = new Map(run.calls.map((call) => [call.seq, call]));
|
|
72
|
+
const accepted = [];
|
|
73
|
+
const rejected = [];
|
|
74
|
+
for (const claim of output.claims) {
|
|
75
|
+
if (claim.citations.length === 0) {
|
|
76
|
+
rejected.push({ claim, reasons: ["no citation for this claim"] });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const reasons = claim.citations
|
|
80
|
+
.map((citation) => checkCitation(citation, callsBySeq))
|
|
81
|
+
.filter((reason) => reason !== null);
|
|
82
|
+
if (reasons.length === 0) {
|
|
83
|
+
accepted.push(claim);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
rejected.push({ claim, reasons });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { accepted, rejected };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=fact-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fact-boundary.js","sourceRoot":"","sources":["../../src/governance/fact-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAyCH,SAAS,cAAc,CAAC,KAAc,EAAE,IAAoC;IAC1E,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;QACzG,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,GAAI,MAA+C,CAAC,GAAG,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,QAAkB,EAAE,UAA6C;IACtF,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,gBAAgB,QAAQ,CAAC,GAAG,oDAAoD,CAAC;IAC1F,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/E,OAAO,gBAAgB,QAAQ,CAAC,GAAG,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,6CAA6C,CAAC;IACzH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAkB,EAAE,GAAgB;IACpE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEtE,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC;YAClE,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS;aAC5B,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;aACtD,MAAM,CAAC,CAAC,MAAM,EAAoB,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;QAEzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The governance funnel — architecture §20.1.
|
|
3
|
+
*
|
|
4
|
+
* > *"Without this layer, a run that surfaces 200 findings gets read by
|
|
5
|
+
* > nobody. Volume without governance is functionally the same as no output at
|
|
6
|
+
* > all."*
|
|
7
|
+
*
|
|
8
|
+
* §20.1 draws a five-filter funnel ending in a hard cap. Two of its boxes had
|
|
9
|
+
* no implementation anywhere: **F2, "known false positives from the feedback
|
|
10
|
+
* store,"** and **the HARD CAP itself**, whose value architecture §32 item 6
|
|
11
|
+
* has carried as a blank since the document was written (*"Hard cap value —
|
|
12
|
+
* how many findings is 'a few'"*, Tuning, *"set from early runs"*).
|
|
13
|
+
*
|
|
14
|
+
* `CLAUDE.md` rule 5 — *"a few high-confidence findings, never an exhaustive
|
|
15
|
+
* maybe-list"* — is the rule aimed most directly at what this whole category
|
|
16
|
+
* of tool gets wrong, and it was the one rule with no number behind it. Three
|
|
17
|
+
* per-check caps of 25 existed (`contracts/orphans.ts`, `validation/rollback.ts`,
|
|
18
|
+
* `validation/env.ts`), which is not the same thing: three checks at 25 present
|
|
19
|
+
* 75 items and nothing objects.
|
|
20
|
+
*
|
|
21
|
+
* ## The cap values, and where they come from
|
|
22
|
+
*
|
|
23
|
+
* §20.1 states its own worked example in prose: *"Twenty-seven possible issues
|
|
24
|
+
* get ignored. Three confirmed issues plus two worth checking get acted on
|
|
25
|
+
* immediately."* **Three plus two is five**, and that is `pr`'s cap — read off
|
|
26
|
+
* the architecture rather than invented.
|
|
27
|
+
*
|
|
28
|
+
* The other three are scaled to their trigger the same way `NODE_BUDGETS`
|
|
29
|
+
* already is, and for the same reason: an IDE hover and a nightly sweep are
|
|
30
|
+
* not the same reading surface. **Starting values, not measurements** —
|
|
31
|
+
* `CLAUDE.md`'s standing convention for every number in this project.
|
|
32
|
+
*
|
|
33
|
+
* ## What this module deliberately does not own
|
|
34
|
+
*
|
|
35
|
+
* - **It does not assign a category.** Only G4 does (`ai-governance-spec.md`
|
|
36
|
+
* §11). This runs strictly after, on already-categorised findings, and
|
|
37
|
+
* reorders and truncates them without touching a single field.
|
|
38
|
+
* - **It does not store dismissals.** `isDismissed` is a caller-supplied
|
|
39
|
+
* predicate. The ledger is `descry-desktop`'s (§21.3 puts the feedback store
|
|
40
|
+
* next to the runs it belongs to); the *filter* is governance and belongs
|
|
41
|
+
* here, next to G1–G4.
|
|
42
|
+
* - **It never silently drops anything.** Every finding that entered leaves in
|
|
43
|
+
* exactly one of `reported` / `unconfirmed` / `suppressed`, and the count it
|
|
44
|
+
* truncated is a structured field with a rendered sentence — the same rule
|
|
45
|
+
* `ai-tool-contract.md` §4 technique 5 applies one layer down (*"a capped
|
|
46
|
+
* result that looks complete is the same defect as a zero that looks like a
|
|
47
|
+
* finding"*).
|
|
48
|
+
*/
|
|
49
|
+
import { type Finding } from "@descryy/ir";
|
|
50
|
+
import type { Trigger } from "../scoping/tiers.ts";
|
|
51
|
+
/**
|
|
52
|
+
* How many findings one run may report, per trigger.
|
|
53
|
+
*
|
|
54
|
+
* `pr` is architecture §20.1's own arithmetic (3 confirmed + 2 worth
|
|
55
|
+
* checking). The rest are scaled to the reading surface, the same shape
|
|
56
|
+
* `NODE_BUDGETS` uses, and are starting values pending real runs to tune from.
|
|
57
|
+
*/
|
|
58
|
+
export declare const FINDING_CAPS: {
|
|
59
|
+
/** A hover is one line of screen. More than one finding there is a list nobody reads. */
|
|
60
|
+
readonly "ide-hover": 1;
|
|
61
|
+
/** A developer is mid-push and blocked. Only the strongest survive. */
|
|
62
|
+
readonly "pre-push": 3;
|
|
63
|
+
/** §20.1's own worked example. */
|
|
64
|
+
readonly pr: 5;
|
|
65
|
+
/** Someone opened a report deliberately and is reading it. Looser, still capped. */
|
|
66
|
+
readonly nightly: 10;
|
|
67
|
+
};
|
|
68
|
+
export interface FindingFunnelOptions {
|
|
69
|
+
readonly trigger: Trigger;
|
|
70
|
+
/**
|
|
71
|
+
* F2. `true` suppresses the finding. Supplied by the caller's feedback store
|
|
72
|
+
* — see the module doc for why the ledger is not here.
|
|
73
|
+
*/
|
|
74
|
+
readonly isDismissed?: (finding: Finding) => boolean;
|
|
75
|
+
/** Overrides the trigger's cap. For a caller that has measured its own; not a way to opt out. */
|
|
76
|
+
readonly cap?: number;
|
|
77
|
+
}
|
|
78
|
+
export interface FindingTruncation {
|
|
79
|
+
readonly total: number;
|
|
80
|
+
readonly shown: number;
|
|
81
|
+
readonly dropped: number;
|
|
82
|
+
/** A rendered sentence when anything was dropped, `null` when nothing was. Never omitted silently. */
|
|
83
|
+
readonly disclosure: string | null;
|
|
84
|
+
}
|
|
85
|
+
export interface FindingFunnelResult {
|
|
86
|
+
/** The capped, ranked, confident list — what a developer reads first. */
|
|
87
|
+
readonly reported: readonly Finding[];
|
|
88
|
+
/** §20.1's own parallel list. Not capped: it is already labelled as unproven. */
|
|
89
|
+
readonly unconfirmed: readonly Finding[];
|
|
90
|
+
/** Removed by F2, kept so the run can say what it hid and why that is not a silence. */
|
|
91
|
+
readonly suppressed: readonly Finding[];
|
|
92
|
+
readonly truncation: FindingTruncation;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Run §20.1's funnel over one run's findings.
|
|
96
|
+
*
|
|
97
|
+
* Pure and total: same input, same output, and every input finding appears in
|
|
98
|
+
* exactly one output list. Deterministic ordering — required by
|
|
99
|
+
* `ai-layer-plan.md` §9 layer 6's replay guarantee, so ties break on the
|
|
100
|
+
* finding's own fingerprint rather than on input order.
|
|
101
|
+
*/
|
|
102
|
+
export declare function runFindingFunnel(findings: readonly Finding[], options: FindingFunnelOptions): FindingFunnelResult;
|
|
103
|
+
/**
|
|
104
|
+
* Re-exported so a caller wiring the funnel and its ledger imports one module.
|
|
105
|
+
* The implementation lives in `@descryy/ir`'s identity module — see
|
|
106
|
+
* `DEC-NEXT-edge-id-prefix-split` for why an identity gets exactly one home.
|
|
107
|
+
*/
|
|
108
|
+
export { findingFingerprint } from "@descryy/ir";
|
|
109
|
+
//# sourceMappingURL=finding-funnel.d.ts.map
|