mandrel 2.55.0 → 2.57.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/.agents/agents/plan-critic.md +13 -18
- package/.agents/agents/story-worker.md +25 -34
- package/.agents/docs/agentrc-reference.json +4 -30
- package/.agents/docs/configuration.md +11 -28
- package/.agents/docs/execution-reference.md +5 -5
- package/.agents/docs/quality-gates.md +8 -7
- package/.agents/instructions.md +9 -10
- package/.agents/rules/ci-remediation.md +39 -21
- package/.agents/schemas/agentrc.schema.json +28 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/audit-to-stories.js +222 -75
- package/.agents/scripts/ceremony-derive.js +191 -0
- package/.agents/scripts/check-context-budget.js +28 -33
- package/.agents/scripts/check-cyclomatic.js +4 -3
- package/.agents/scripts/deliver-light.js +31 -94
- package/.agents/scripts/file-ci-gap.js +306 -0
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
- package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
- package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
- package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
- package/.agents/scripts/lib/close-validation/gates.js +52 -1
- package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
- package/.agents/scripts/lib/config/delivery-routing.js +7 -33
- package/.agents/scripts/lib/config/explain.js +0 -19
- package/.agents/scripts/lib/config/limits.js +18 -78
- package/.agents/scripts/lib/config/quality.js +6 -3
- package/.agents/scripts/lib/config/runners.js +3 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
- package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
- package/.agents/scripts/lib/config-settings-schema.js +49 -143
- package/.agents/scripts/lib/crap-engine.js +35 -4
- package/.agents/scripts/lib/crap-utils.js +17 -1
- package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +53 -13
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
- package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
- package/.agents/scripts/lib/findings/route-finding.js +38 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/github/framework-repo.js +148 -2
- package/.agents/scripts/lib/label-constants.js +6 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +2 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
- package/.agents/scripts/lib/orchestration/ci-gap-intake.js +605 -0
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
- package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
- package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
- package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
- package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
- package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +197 -0
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +133 -299
- package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
- package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
- package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
- package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
- package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
- package/.agents/scripts/lib/story-body/story-body.js +17 -237
- package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
- package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
- package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
- package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
- package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
- package/.agents/scripts/lib/test-run-credit.js +266 -0
- package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
- package/.agents/scripts/lib/workers/crap-worker.js +32 -41
- package/.agents/scripts/plan-context.js +7 -9
- package/.agents/scripts/plan-critics.js +28 -54
- package/.agents/scripts/plan-persist.js +25 -68
- package/.agents/scripts/pr-watch-with-update.js +3 -2
- package/.agents/scripts/quality-preview.js +51 -0
- package/.agents/scripts/run-tests.js +12 -0
- package/.agents/scripts/stories-wave-tick.js +23 -45
- package/.agents/scripts/test-isolate.js +13 -180
- package/.agents/scripts/update-coverage-baseline.js +25 -70
- package/.agents/scripts/update-crap-baseline.js +19 -123
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/workflows/audit-clean-code.md +4 -3
- package/.agents/workflows/audit-to-stories.md +63 -27
- package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
- package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
- package/.agents/workflows/helpers/code-review.md +2 -3
- package/.agents/workflows/helpers/deliver-digest.md +41 -57
- package/.agents/workflows/helpers/deliver-light.md +40 -105
- package/.agents/workflows/helpers/deliver-reference.md +1 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +56 -62
- package/.agents/workflows/helpers/deliver-story.md +9 -13
- package/.agents/workflows/helpers/plan-reference.md +132 -196
- package/.agents/workflows/mandrel-plan.md +28 -41
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +33 -0
- package/lib/migrations/index.js +4 -0
- package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
- package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
- package/package.json +1 -1
- package/.agents/scripts/lib/framework-version.js +0 -39
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
- package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
- package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
|
@@ -1,6 +1,152 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* framework-repo.js — the follow-up **ownership routing** SSOT: which
|
|
3
|
+
* repository a finding, a retro proposal, or a CI-gap intake issue is filed
|
|
4
|
+
* in, and what to say when that question has no answer.
|
|
5
|
+
*
|
|
6
|
+
* ## Three buckets, not two
|
|
7
|
+
*
|
|
8
|
+
* A defect surfaced by one repository's CI is not necessarily that
|
|
9
|
+
* repository's to fix. Ownership splits three ways:
|
|
10
|
+
*
|
|
11
|
+
* - `consumer` — the repo the run is standing in (`github.owner`/`repo`).
|
|
12
|
+
* - `framework` — the Mandrel framework itself
|
|
13
|
+
* (`github.followUpRepos.framework`, defaulted to the mirror constant).
|
|
14
|
+
* - `platform` — a shared platform / infrastructure repo that neither of
|
|
15
|
+
* the other two owns: a shared base config, a runner fleet, a
|
|
16
|
+
* cross-repo toolchain (`github.followUpRepos.platform`, **no default**
|
|
17
|
+
* — nothing can guess a shared repo's slug).
|
|
18
|
+
*
|
|
19
|
+
* ## Why there is no `?? currentRepo` fallback
|
|
20
|
+
*
|
|
21
|
+
* The two-bucket predecessor resolved a framework-tagged item with
|
|
22
|
+
* `frameworkRepo ? frameworkRepo : currentRepo`. When the config key was
|
|
23
|
+
* absent that expression filed framework-owned work into the **consumer's**
|
|
24
|
+
* repo while the rendered retro claimed it went to the framework repo — a
|
|
25
|
+
* silent mis-file, recorded in `retro-proposals-graduator.js`'s own file-top
|
|
26
|
+
* comment. The failure was invisible in this repository precisely because
|
|
27
|
+
* consumer === framework here.
|
|
28
|
+
*
|
|
29
|
+
* So an unresolvable bucket is a first-class outcome, never a fallback:
|
|
30
|
+
* `routeOwnership` returns `routable: false` plus the `missingKey` that
|
|
31
|
+
* would fix it, and every caller must decide **out loud** what to do with
|
|
32
|
+
* that — file locally and say so in the body (the CI-gap filer), or defer
|
|
33
|
+
* and surface it where an operator will see it (the graduators). What no
|
|
34
|
+
* caller may do is route it somewhere plausible and stay quiet.
|
|
4
35
|
*/
|
|
5
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Canonical framework repository slug, used when the consumer config does
|
|
39
|
+
* not supply `github.followUpRepos.framework`. The `framework` bucket is the
|
|
40
|
+
* one bucket with a knowable default: it is this framework.
|
|
41
|
+
*/
|
|
6
42
|
export const DEFAULT_FRAMEWORK_REPO = 'dsj1984/mandrel';
|
|
43
|
+
|
|
44
|
+
/** The closed ownership-bucket set. */
|
|
45
|
+
export const OWNERSHIP_BUCKETS = Object.freeze([
|
|
46
|
+
'consumer',
|
|
47
|
+
'framework',
|
|
48
|
+
'platform',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The `.agentrc.json` key behind each bucket, quoted verbatim when a bucket
|
|
53
|
+
* is unroutable so the operator is told which key to set rather than that
|
|
54
|
+
* "routing failed".
|
|
55
|
+
*/
|
|
56
|
+
const OWNERSHIP_CONFIG_KEYS = Object.freeze({
|
|
57
|
+
consumer: 'github.owner / github.repo',
|
|
58
|
+
framework: 'github.followUpRepos.framework',
|
|
59
|
+
platform: 'github.followUpRepos.platform',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse an `"<owner>/<repo>"` slug into `{ owner, repo }`, or `null` when the
|
|
64
|
+
* slug is absent, empty, or malformed. A `null` return is the signal an
|
|
65
|
+
* unroutable bucket is built from — never a reason to substitute another
|
|
66
|
+
* repo.
|
|
67
|
+
*
|
|
68
|
+
* @param {string|null|undefined} slug
|
|
69
|
+
* @returns {{ owner: string, repo: string } | null}
|
|
70
|
+
*/
|
|
71
|
+
export function parseRepoSlug(slug) {
|
|
72
|
+
if (typeof slug !== 'string') return null;
|
|
73
|
+
const parts = slug.trim().split('/');
|
|
74
|
+
if (parts.length !== 2) return null;
|
|
75
|
+
const [owner, repo] = parts;
|
|
76
|
+
if (!owner || !repo) return null;
|
|
77
|
+
return { owner, repo };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render a `{ owner, repo }` pair back to its slug, or `null` when the pair
|
|
82
|
+
* is absent/malformed.
|
|
83
|
+
*
|
|
84
|
+
* @param {{ owner?: string, repo?: string }|null|undefined} repo
|
|
85
|
+
* @returns {string|null}
|
|
86
|
+
*/
|
|
87
|
+
export function formatRepoSlug(repo) {
|
|
88
|
+
if (!repo || typeof repo !== 'object') return null;
|
|
89
|
+
const { owner, repo: name } = /** @type {{owner?: string, repo?: string}} */ (
|
|
90
|
+
repo
|
|
91
|
+
);
|
|
92
|
+
if (typeof owner !== 'string' || typeof name !== 'string') return null;
|
|
93
|
+
if (!owner.trim() || !name.trim()) return null;
|
|
94
|
+
return `${owner.trim()}/${name.trim()}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve every ownership bucket from a resolved `.agentrc` config.
|
|
99
|
+
*
|
|
100
|
+
* `framework` falls back to {@link DEFAULT_FRAMEWORK_REPO}; `platform` has no
|
|
101
|
+
* default and stays `null` when unconfigured; `consumer` is `null` when
|
|
102
|
+
* `github.owner`/`github.repo` are unset. A `null` bucket is an honest
|
|
103
|
+
* "unknown", which {@link routeOwnership} turns into a named, reportable
|
|
104
|
+
* outcome.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} [config] — resolved `.agentrc` config.
|
|
107
|
+
* @returns {{ consumer: ({owner: string, repo: string}|null), framework: ({owner: string, repo: string}|null), platform: ({owner: string, repo: string}|null) }}
|
|
108
|
+
*/
|
|
109
|
+
export function resolveOwnershipRepos(config) {
|
|
110
|
+
const github = config?.github ?? {};
|
|
111
|
+
const followUp = github?.followUpRepos ?? {};
|
|
112
|
+
const owner = typeof github.owner === 'string' ? github.owner.trim() : '';
|
|
113
|
+
const repo = typeof github.repo === 'string' ? github.repo.trim() : '';
|
|
114
|
+
return {
|
|
115
|
+
consumer: owner && repo ? { owner, repo } : null,
|
|
116
|
+
framework:
|
|
117
|
+
parseRepoSlug(followUp.framework) ??
|
|
118
|
+
parseRepoSlug(DEFAULT_FRAMEWORK_REPO),
|
|
119
|
+
platform: parseRepoSlug(followUp.platform),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Route one ownership bucket to the repository its work belongs in.
|
|
125
|
+
*
|
|
126
|
+
* Total: an unknown bucket, an absent repos map, and an unconfigured bucket
|
|
127
|
+
* all resolve to `routable: false` with the `missingKey` that would fix it —
|
|
128
|
+
* never to a substituted repository.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} opts
|
|
131
|
+
* @param {string} opts.bucket — one of {@link OWNERSHIP_BUCKETS}.
|
|
132
|
+
* @param {{consumer?: object|null, framework?: object|null, platform?: object|null}} opts.repos
|
|
133
|
+
* — resolved buckets, from {@link resolveOwnershipRepos} or assembled by a
|
|
134
|
+
* caller that already holds the repo objects.
|
|
135
|
+
* @param {{owner: string, repo: string}|null} [opts.currentRepo] — the repo
|
|
136
|
+
* the run is standing in, used only to report `crossRepo`.
|
|
137
|
+
* @returns {{ bucket: string, routedRepo: ({owner: string, repo: string}|null), routable: boolean, missingKey: (string|null), crossRepo: boolean }}
|
|
138
|
+
*/
|
|
139
|
+
export function routeOwnership({ bucket, repos, currentRepo = null } = {}) {
|
|
140
|
+
const known = OWNERSHIP_BUCKETS.includes(bucket);
|
|
141
|
+
const routedRepo = known ? (repos?.[bucket] ?? null) : null;
|
|
142
|
+
const routable = Boolean(routedRepo?.owner && routedRepo?.repo);
|
|
143
|
+
const missingKey = routable
|
|
144
|
+
? null
|
|
145
|
+
: (OWNERSHIP_CONFIG_KEYS[bucket] ?? `unknown ownership bucket "${bucket}"`);
|
|
146
|
+
const crossRepo =
|
|
147
|
+
routable &&
|
|
148
|
+
Boolean(currentRepo) &&
|
|
149
|
+
(routedRepo.owner !== currentRepo.owner ||
|
|
150
|
+
routedRepo.repo !== currentRepo.repo);
|
|
151
|
+
return { bucket, routedRepo, routable, missingKey, crossRepo };
|
|
152
|
+
}
|
|
@@ -119,7 +119,11 @@ export const ACCEPTANCE_NA = ACCEPTANCE_LABELS.N_A;
|
|
|
119
119
|
* loop). `meta::framework-gap` is applied to issues that surface a defect or
|
|
120
120
|
* missing capability in the framework itself; `meta::consumer-improvement`
|
|
121
121
|
* is applied to issues that surface improvements to a consumer project
|
|
122
|
-
* (workflow tweaks, ergonomic asks, doc polish)
|
|
122
|
+
* (workflow tweaks, ergonomic asks, doc polish); `meta::platform-gap` is
|
|
123
|
+
* applied to issues owned by neither — a shared base config, a runner fleet,
|
|
124
|
+
* a cross-repo toolchain. The three mirror the ownership buckets in
|
|
125
|
+
* `lib/github/framework-repo.js`, so a routed filing's label and its
|
|
126
|
+
* destination repository cannot disagree. The `/mandrel-plan` Phase 0
|
|
123
127
|
* fetcher (see `lib/feedback-loop/prior-feedback-fetcher.js`) reads open
|
|
124
128
|
* issues carrying either label and surfaces them to the planner so retro
|
|
125
129
|
* signals are routed into durable substrates rather than lost in chat.
|
|
@@ -127,6 +131,7 @@ export const ACCEPTANCE_NA = ACCEPTANCE_LABELS.N_A;
|
|
|
127
131
|
export const META_LABELS = {
|
|
128
132
|
FRAMEWORK_GAP: 'meta::framework-gap',
|
|
129
133
|
CONSUMER_IMPROVEMENT: 'meta::consumer-improvement',
|
|
134
|
+
PLATFORM_GAP: 'meta::platform-gap',
|
|
130
135
|
};
|
|
131
136
|
|
|
132
137
|
/**
|
|
@@ -76,7 +76,7 @@ export const RUNTIME_FRICTION_CATEGORIES = Object.freeze({
|
|
|
76
76
|
*/
|
|
77
77
|
TOOL_DEGRADED: 'tool-degraded',
|
|
78
78
|
/**
|
|
79
|
-
* The light delivery path refused a scope — a suitability-gate
|
|
79
|
+
* The light delivery path refused a scope — a suitability-gate escalation
|
|
80
80
|
* or a blocked diff backstop. Story #4856 added it because neither rejection
|
|
81
81
|
* emitted anything, so an over-tight ceiling could only reach the framework
|
|
82
82
|
* as anecdote; the roll-up aggregating these by category is what makes the
|
|
@@ -107,6 +107,7 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
|
|
|
107
107
|
'check-workflow-citations.js',
|
|
108
108
|
'check-workflow-cli-lint.js',
|
|
109
109
|
'check-workflow-timeouts.js',
|
|
110
|
+
'ceremony-derive.js',
|
|
110
111
|
'cleanup-repo-test-temp.js',
|
|
111
112
|
'coverage-capture.js',
|
|
112
113
|
'deliver-light.js',
|
|
@@ -115,6 +116,7 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
|
|
|
115
116
|
'diagnose.js',
|
|
116
117
|
'drain-pending-cleanup.js',
|
|
117
118
|
'evidence-gate.js',
|
|
119
|
+
'file-ci-gap.js',
|
|
118
120
|
'generate-config-docs.js',
|
|
119
121
|
'generate-lens-checklists.js',
|
|
120
122
|
'generate-skills-index.js',
|
|
@@ -43,11 +43,12 @@
|
|
|
43
43
|
* The Story escalates to `agent::blocked`; it never
|
|
44
44
|
* silently proceeds to close.
|
|
45
45
|
*
|
|
46
|
-
* ## The
|
|
46
|
+
* ## The cap
|
|
47
47
|
*
|
|
48
|
-
* `maxRounds` arrives
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* `maxRounds` arrives normalized by `lib/config/acceptance-eval.js` as a
|
|
49
|
+
* non-negative integer (Story #5313: `0` means "score once, no redraft").
|
|
50
|
+
* This reducer maps that onto an effective cap of scored rounds: a cap of
|
|
51
|
+
* `0`, a negative value or a non-integer is coerced to 1, so there is no
|
|
51
52
|
* input — config or verdict — that yields an unbounded `redraft` chain.
|
|
52
53
|
* When `round >= effectiveCap` and criteria remain unmet, the only
|
|
53
54
|
* possible action is `block`.
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
*
|
|
20
20
|
* ## Ceremony profiles (`delivery.routing.ceremonyProfile`)
|
|
21
21
|
*
|
|
22
|
-
* - `minimal` — always `inline` (
|
|
22
|
+
* - `minimal` — always `inline` (no fresh critic spawn).
|
|
23
23
|
* Use for tiny N=1 Stories the operator trusts.
|
|
24
|
-
* - `standard` — level-routed (default). Low → inline
|
|
25
|
-
*
|
|
24
|
+
* - `standard` — level-routed (default). Low → inline; high or
|
|
25
|
+
* underivable → fresh.
|
|
26
26
|
* - `strict` — always `fresh` regardless of the derived level.
|
|
27
27
|
*
|
|
28
28
|
* ## The load-bearing invariant (M4-B acceptance floor — DO NOT VIOLATE)
|
|
@@ -51,9 +51,7 @@
|
|
|
51
51
|
*
|
|
52
52
|
* - `high` level → `fresh` (a sensitive path was touched — a
|
|
53
53
|
* fresh-context maker-blind spawn).
|
|
54
|
-
* - `low` level → `inline` (the contract-identical inline critic)
|
|
55
|
-
* UNLESS the maker-checker sampling floor
|
|
56
|
-
* selects this cluster → `fresh`.
|
|
54
|
+
* - `low` level → `inline` (the contract-identical inline critic).
|
|
57
55
|
* - missing / unknown → `fresh` (fail-safe: the diff could not be
|
|
58
56
|
* enumerated, so there is no evidence the
|
|
59
57
|
* change is unremarkable; treat it as
|
|
@@ -61,15 +59,14 @@
|
|
|
61
59
|
* exactly as `resolveDepth` degrades to
|
|
62
60
|
* `standard` on the same signal).
|
|
63
61
|
*
|
|
64
|
-
* ##
|
|
62
|
+
* ## No sampling floor (Story #5313)
|
|
65
63
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* ignore the sampling floor.
|
|
64
|
+
* The maker-checker sampling floor (`delivery.routing.freshCriticSampleRate`,
|
|
65
|
+
* `sampledFresh`) bounded independent checking by a cluster-index stride —
|
|
66
|
+
* a count, not a risk signal — and was retired with the delivery diet. The
|
|
67
|
+
* standard profile now routes purely off the derived level, so the decision
|
|
68
|
+
* carries no `sampled` field and `clusterIndex` is accepted only for call-site
|
|
69
|
+
* compatibility (it never changes the outcome).
|
|
73
70
|
*
|
|
74
71
|
* Pure and total: inputs in, decision out. No I/O, no throws. `null` /
|
|
75
72
|
* `undefined` / malformed inputs degrade to `fresh` + `full` ceremony.
|
|
@@ -119,49 +116,19 @@ function normalizeCeremonyProfile(value) {
|
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* `round(1 / r)` and every `stride`-th cluster (0-based indices 0, stride,
|
|
126
|
-
* 2·stride, …) is forced fresh, yielding ≈`r` of clusters fresh. `r <= 0`
|
|
127
|
-
* disables the floor (no cluster forced); `r >= 1` forces every cluster.
|
|
128
|
-
*
|
|
129
|
-
* @param {number} clusterIndex Zero-based cluster position (from the
|
|
130
|
-
* caller-owned fan-out — an INPUT, never mutated here).
|
|
131
|
-
* @param {number} rate Sampling rate, already clamped into [0, 1] by
|
|
132
|
-
* `getDeliveryRouting`.
|
|
133
|
-
* @returns {boolean} `true` when the floor forces this cluster fresh.
|
|
134
|
-
*/
|
|
135
|
-
export function sampledFresh(clusterIndex, rate) {
|
|
136
|
-
if (typeof rate !== 'number' || !Number.isFinite(rate) || rate <= 0) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
if (rate >= 1) return true;
|
|
140
|
-
const idx =
|
|
141
|
-
typeof clusterIndex === 'number' &&
|
|
142
|
-
Number.isInteger(clusterIndex) &&
|
|
143
|
-
clusterIndex >= 0
|
|
144
|
-
? clusterIndex
|
|
145
|
-
: 0;
|
|
146
|
-
const stride = Math.max(1, Math.round(1 / rate));
|
|
147
|
-
return idx % stride === 0;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Resolve the acceptance ceremony for one cluster from the ceremony profile,
|
|
152
|
-
* the derived change level, and the maker-checker sampling floor. See the
|
|
153
|
-
* module header for the tier rules and the untouchable cluster-count invariant.
|
|
119
|
+
* Resolve the acceptance ceremony for one cluster from the ceremony profile
|
|
120
|
+
* and the derived change level. See the module header for the tier rules and
|
|
121
|
+
* the untouchable cluster-count invariant.
|
|
154
122
|
*
|
|
155
123
|
* @param {{
|
|
156
124
|
* derivedLevel?: (ChangeLevel|string|null|undefined),
|
|
157
125
|
* clusterIndex?: (number|null|undefined),
|
|
158
|
-
* freshCriticSampleRate?: (number|null|undefined),
|
|
159
126
|
* ceremonyProfile?: (CeremonyProfile|string|null|undefined),
|
|
160
|
-
* }} [input]
|
|
127
|
+
* }} [input] `clusterIndex` is accepted for call-site compatibility only
|
|
128
|
+
* (Story #5313 retired the sampling floor that read it).
|
|
161
129
|
* @returns {{
|
|
162
130
|
* mode: CeremonyMode,
|
|
163
131
|
* reason: string,
|
|
164
|
-
* sampled: boolean,
|
|
165
132
|
* profile: CeremonyProfile,
|
|
166
133
|
* verdictOwner: VerdictOwner,
|
|
167
134
|
* }}
|
|
@@ -172,27 +139,20 @@ export function resolveCeremonyForRisk(input = {}) {
|
|
|
172
139
|
}
|
|
173
140
|
|
|
174
141
|
/**
|
|
175
|
-
* Internal mode/reason resolution — the tier rules
|
|
176
|
-
*
|
|
177
|
-
*
|
|
142
|
+
* Internal mode/reason resolution — the tier rules. `resolveCeremonyForRisk`
|
|
143
|
+
* decorates the result with the single `verdictOwner` derived from the mode
|
|
144
|
+
* (Story #4723).
|
|
178
145
|
*
|
|
179
146
|
* @param {Parameters<typeof resolveCeremonyForRisk>[0]} [input]
|
|
180
147
|
* @returns {{
|
|
181
148
|
* mode: CeremonyMode,
|
|
182
149
|
* reason: string,
|
|
183
|
-
* sampled: boolean,
|
|
184
150
|
* profile: CeremonyProfile,
|
|
185
151
|
* }}
|
|
186
152
|
*/
|
|
187
153
|
function resolveCeremonyDecision(input = {}) {
|
|
188
154
|
const derivedLevel =
|
|
189
155
|
input && typeof input === 'object' ? input.derivedLevel : undefined;
|
|
190
|
-
const clusterIndex =
|
|
191
|
-
input && typeof input === 'object' ? input.clusterIndex : undefined;
|
|
192
|
-
const rate =
|
|
193
|
-
input && typeof input === 'object'
|
|
194
|
-
? input.freshCriticSampleRate
|
|
195
|
-
: undefined;
|
|
196
156
|
const profile = normalizeCeremonyProfile(
|
|
197
157
|
input && typeof input === 'object' ? input.ceremonyProfile : undefined,
|
|
198
158
|
);
|
|
@@ -201,7 +161,6 @@ function resolveCeremonyDecision(input = {}) {
|
|
|
201
161
|
return {
|
|
202
162
|
mode: 'inline',
|
|
203
163
|
reason: 'ceremonyProfile=minimal: inline critic (no fresh spawn)',
|
|
204
|
-
sampled: false,
|
|
205
164
|
profile,
|
|
206
165
|
};
|
|
207
166
|
}
|
|
@@ -209,7 +168,6 @@ function resolveCeremonyDecision(input = {}) {
|
|
|
209
168
|
return {
|
|
210
169
|
mode: 'fresh',
|
|
211
170
|
reason: 'ceremonyProfile=strict: fresh-context critic',
|
|
212
|
-
sampled: false,
|
|
213
171
|
profile,
|
|
214
172
|
};
|
|
215
173
|
}
|
|
@@ -218,24 +176,13 @@ function resolveCeremonyDecision(input = {}) {
|
|
|
218
176
|
return {
|
|
219
177
|
mode: 'fresh',
|
|
220
178
|
reason: 'sensitive path touched: fresh-context critic',
|
|
221
|
-
sampled: false,
|
|
222
179
|
profile,
|
|
223
180
|
};
|
|
224
181
|
}
|
|
225
182
|
if (derivedLevel === 'low') {
|
|
226
|
-
if (sampledFresh(clusterIndex, rate)) {
|
|
227
|
-
return {
|
|
228
|
-
mode: 'fresh',
|
|
229
|
-
reason:
|
|
230
|
-
'low-level cluster forced fresh by the maker-checker sampling floor',
|
|
231
|
-
sampled: true,
|
|
232
|
-
profile,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
183
|
return {
|
|
236
184
|
mode: 'inline',
|
|
237
185
|
reason: 'no sensitive path touched: contract-identical inline critic',
|
|
238
|
-
sampled: false,
|
|
239
186
|
profile,
|
|
240
187
|
};
|
|
241
188
|
}
|
|
@@ -245,7 +192,6 @@ function resolveCeremonyDecision(input = {}) {
|
|
|
245
192
|
mode: 'fresh',
|
|
246
193
|
reason:
|
|
247
194
|
'change level underivable: fail-safe fresh-context critic + full ceremony',
|
|
248
|
-
sampled: false,
|
|
249
195
|
profile,
|
|
250
196
|
};
|
|
251
197
|
}
|