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.
Files changed (131) hide show
  1. package/.agents/agents/plan-critic.md +13 -18
  2. package/.agents/agents/story-worker.md +25 -34
  3. package/.agents/docs/agentrc-reference.json +4 -30
  4. package/.agents/docs/configuration.md +11 -28
  5. package/.agents/docs/execution-reference.md +5 -5
  6. package/.agents/docs/quality-gates.md +8 -7
  7. package/.agents/instructions.md +9 -10
  8. package/.agents/rules/ci-remediation.md +39 -21
  9. package/.agents/schemas/agentrc.schema.json +28 -185
  10. package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
  11. package/.agents/scripts/acceptance-eval.js +107 -17
  12. package/.agents/scripts/audit-to-stories.js +222 -75
  13. package/.agents/scripts/ceremony-derive.js +191 -0
  14. package/.agents/scripts/check-context-budget.js +28 -33
  15. package/.agents/scripts/check-cyclomatic.js +4 -3
  16. package/.agents/scripts/deliver-light.js +31 -94
  17. package/.agents/scripts/file-ci-gap.js +306 -0
  18. package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
  19. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
  20. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
  21. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
  22. package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
  23. package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
  24. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
  25. package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
  26. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
  27. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  28. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  29. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  30. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  31. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  32. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  33. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  34. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  35. package/.agents/scripts/lib/config/explain.js +0 -19
  36. package/.agents/scripts/lib/config/limits.js +18 -78
  37. package/.agents/scripts/lib/config/quality.js +6 -3
  38. package/.agents/scripts/lib/config/runners.js +3 -2
  39. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  40. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  41. package/.agents/scripts/lib/config-settings-schema.js +49 -143
  42. package/.agents/scripts/lib/crap-engine.js +35 -4
  43. package/.agents/scripts/lib/crap-utils.js +17 -1
  44. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  45. package/.agents/scripts/lib/feedback-loop/graduator-core.js +53 -13
  46. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
  47. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
  48. package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
  49. package/.agents/scripts/lib/findings/route-finding.js +38 -0
  50. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  51. package/.agents/scripts/lib/github/framework-repo.js +148 -2
  52. package/.agents/scripts/lib/label-constants.js +6 -1
  53. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  54. package/.agents/scripts/lib/observability/source-classifier.js +2 -0
  55. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  56. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  57. package/.agents/scripts/lib/orchestration/ci-gap-intake.js +605 -0
  58. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
  59. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  60. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  61. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  62. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  63. package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
  64. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  65. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  66. package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +197 -0
  67. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
  68. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
  69. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +133 -299
  70. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  71. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  72. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  73. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
  74. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  75. package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
  76. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  77. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  78. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  79. package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
  80. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  81. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  82. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  83. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  84. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  85. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  86. package/.agents/scripts/lib/story-body/story-body.js +17 -237
  87. package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
  88. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  89. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  90. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  91. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  92. package/.agents/scripts/lib/test-run-credit.js +266 -0
  93. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  94. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  95. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  96. package/.agents/scripts/plan-context.js +7 -9
  97. package/.agents/scripts/plan-critics.js +28 -54
  98. package/.agents/scripts/plan-persist.js +25 -68
  99. package/.agents/scripts/pr-watch-with-update.js +3 -2
  100. package/.agents/scripts/quality-preview.js +51 -0
  101. package/.agents/scripts/run-tests.js +12 -0
  102. package/.agents/scripts/stories-wave-tick.js +23 -45
  103. package/.agents/scripts/test-isolate.js +13 -180
  104. package/.agents/scripts/update-coverage-baseline.js +25 -70
  105. package/.agents/scripts/update-crap-baseline.js +19 -123
  106. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  107. package/.agents/workflows/audit-clean-code.md +4 -3
  108. package/.agents/workflows/audit-to-stories.md +63 -27
  109. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  110. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  111. package/.agents/workflows/helpers/code-review.md +2 -3
  112. package/.agents/workflows/helpers/deliver-digest.md +41 -57
  113. package/.agents/workflows/helpers/deliver-light.md +40 -105
  114. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  115. package/.agents/workflows/helpers/deliver-story-reference.md +56 -62
  116. package/.agents/workflows/helpers/deliver-story.md +9 -13
  117. package/.agents/workflows/helpers/plan-reference.md +132 -196
  118. package/.agents/workflows/mandrel-plan.md +28 -41
  119. package/.agents/workflows/memory-consolidate.md +9 -13
  120. package/docs/CHANGELOG.md +33 -0
  121. package/lib/migrations/index.js +4 -0
  122. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  123. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  124. package/package.json +1 -1
  125. package/.agents/scripts/lib/framework-version.js +0 -39
  126. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  127. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  128. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  129. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  130. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  131. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -1,6 +1,152 @@
1
1
  /**
2
- * Canonical framework repository slug used by follow-up / graduator
3
- * paths when the consumer config does not supply owner/repo.
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). The `/mandrel-plan` Phase 0
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 `ask-operator`
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 undisableable cap
46
+ * ## The cap
47
47
  *
48
- * `maxRounds` arrives already clamped by `lib/config/acceptance-eval.js`
49
- * into `[1, ceiling]`, but this reducer defends the invariant a second
50
- * time: a non-positive or non-integer cap is coerced to 1, so there is no
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` (skip fresh critic + sampling floor).
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 (+ sampling floor);
25
- * high → fresh.
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
- * ## Maker-checker sampling floor
62
+ * ## No sampling floor (Story #5313)
65
63
  *
66
- * Even at a `low` derived level under `standard`, a fraction of clusters
67
- * (`freshCriticSampleRate`, default 0.2) is forced `fresh` so a low level never
68
- * means zero independent checking. The selection is **deterministic** in the
69
- * cluster index (a fixed stride), so it is stable across re-runs and —
70
- * critically — never changes the cluster count: it only re-labels which of
71
- * the fixed set of clusters run fresh. Profiles `minimal` and `strict`
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
- * Decide whether the sampling floor forces this low-risk cluster fresh.
123
- *
124
- * Deterministic in the cluster index: with rate `r` (0 < r ≤ 1) the stride is
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 and sampling floor.
176
- * `resolveCeremonyForRisk` decorates the result with the single
177
- * `verdictOwner` derived from the mode (Story #4723).
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
  }