instar 1.3.1017 → 1.3.1019
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/package.json +1 -1
- package/scripts/lint-rollout-evidence-resolvable.js +27 -4
- package/skills/spec-converge/scripts/write-convergence-tag.mjs +39 -3
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.1018.md +66 -0
- package/upgrades/1.3.1019.md +49 -0
- package/upgrades/side-effects/openq-gate-numbered-headings.md +47 -0
- package/upgrades/side-effects/rollout-evidence-ratchet-composed.md +54 -0
- package/upgrades/1.3.1017.md +0 -37
package/package.json
CHANGED
|
@@ -115,18 +115,39 @@ for (const file of specFiles()) {
|
|
|
115
115
|
try { text = fs.readFileSync(file, 'utf8'); } catch { continue; }
|
|
116
116
|
const fm = frontmatter(text);
|
|
117
117
|
if (!fm) continue;
|
|
118
|
-
|
|
118
|
+
// GUARDED DISPOSITIONS — 'active' AND 'composed'.
|
|
119
|
+
//
|
|
120
|
+
// 'composed' does NOT mean exempt. It names WHO owns graduation (the rollout rides
|
|
121
|
+
// an owner feature via rollout-owner-feature) rather than WHETHER the evidence
|
|
122
|
+
// matters. A composed spec still carries a real rollout-criteria naming a
|
|
123
|
+
// measurement, and often rollout-metrics-json thresholds against it. If its ref
|
|
124
|
+
// 404s the criterion is exactly as unevaluable as an active one's, and the feature
|
|
125
|
+
// parks for exactly the same reason.
|
|
126
|
+
//
|
|
127
|
+
// Widening is safe BECAUSE of assertion C rather than in spite of it: a composed
|
|
128
|
+
// spec whose owner feature was genuinely abandoned may legitimately carry a stale
|
|
129
|
+
// ref, and the shrink-only baseline is where that goes — with a stated reason,
|
|
130
|
+
// deleted automatically the moment the ref starts resolving. So this does not force
|
|
131
|
+
// anyone to FIX anything; it forces them to DECLARE. Entry needs evidence.
|
|
132
|
+
//
|
|
133
|
+
// EARNED (2026-07-27): this lint shipped guarding 'active' only, and its own header
|
|
134
|
+
// said "a sweep of all 5 rollout-active specs" — accurate for its scope, and the
|
|
135
|
+
// scope was narrower than the class it names. A single-pass sweep is incomplete by
|
|
136
|
+
// definition (Iterative Audit to Convergence); this is that standard applied to the
|
|
137
|
+
// guard itself, one pass later.
|
|
138
|
+
const disposition = field(fm, 'rollout-disposition');
|
|
139
|
+
if (disposition !== 'active' && disposition !== 'composed') continue;
|
|
119
140
|
if (field(fm, 'rollout-evidence-type') !== 'endpoint') continue;
|
|
120
141
|
const ref = field(fm, 'rollout-evidence-ref');
|
|
121
142
|
if (!ref || !ref.startsWith('/')) continue; // unparseable ⇒ skip, not fail
|
|
122
143
|
const slug = field(fm, 'slug') || path.basename(file, '.md');
|
|
123
144
|
const resolves = srcContains(ref);
|
|
124
|
-
seenActive.push({ slug, ref, resolves });
|
|
145
|
+
seenActive.push({ slug, ref, resolves, disposition });
|
|
125
146
|
|
|
126
147
|
// A — every active endpoint ref resolves, unless explicitly accepted.
|
|
127
148
|
if (!resolves && !allowed.has(slug)) {
|
|
128
149
|
errors.push(
|
|
129
|
-
`${path.relative(ROOT, file)}: rollout-disposition
|
|
150
|
+
`${path.relative(ROOT, file)}: rollout-disposition:${disposition} names ` +
|
|
130
151
|
`rollout-evidence-ref "${ref}" but no route with that path exists in src/. ` +
|
|
131
152
|
`The graduation criterion can never be evaluated, so this rollout is parked ` +
|
|
132
153
|
`indefinitely. Build the endpoint, correct the ref, or add an explicit ` +
|
|
@@ -157,6 +178,8 @@ if (errors.length) {
|
|
|
157
178
|
process.exit(1);
|
|
158
179
|
}
|
|
159
180
|
console.log(
|
|
160
|
-
`lint-rollout-evidence-resolvable: clean — ${seenActive.length}
|
|
181
|
+
`lint-rollout-evidence-resolvable: clean — ${seenActive.length} guarded endpoint spec(s) ` +
|
|
182
|
+
`(${seenActive.filter((s) => s.disposition === 'active').length} active, ` +
|
|
183
|
+
`${seenActive.filter((s) => s.disposition === 'composed').length} composed), ` +
|
|
161
184
|
`${seenActive.filter((s) => s.resolves).length} resolving, ${allowed.size} accepted-unresolved.`,
|
|
162
185
|
);
|
|
@@ -102,12 +102,44 @@ function parseArgs() {
|
|
|
102
102
|
* Anything else with content (e.g. a `- **Q1:** …` bullet or a paragraph posing
|
|
103
103
|
* a question) is an unresolved entry.
|
|
104
104
|
*/
|
|
105
|
+
/**
|
|
106
|
+
* Builds the H2 matcher for a named gate section.
|
|
107
|
+
*
|
|
108
|
+
* ONE builder, used by BOTH gate sections, deliberately: the numbered-heading
|
|
109
|
+
* hole below existed because the two matchers were written separately and only
|
|
110
|
+
* one of them ever got a heading-variance fix. A shared builder means the next
|
|
111
|
+
* variance fix cannot land on one gate and miss its sibling.
|
|
112
|
+
*
|
|
113
|
+
* Tolerated shapes:
|
|
114
|
+
* - `## Open questions` (canonical)
|
|
115
|
+
* - `## 9. Open questions` (numbered — the hole this closes)
|
|
116
|
+
* - `## 8b. Open questions` / `## 3) …` (lettered / paren'd)
|
|
117
|
+
* - `## 1.2 Open questions` (dotted)
|
|
118
|
+
* - `## Open questions (round 2)` (suffix variant — already worked)
|
|
119
|
+
*
|
|
120
|
+
* Why this was load-bearing: `findOpenQuestions` returns `[]` when the heading
|
|
121
|
+
* does not match, and `[]` means "nothing parked on the user". So a NUMBERED
|
|
122
|
+
* heading made a LIVE, unresolved user-decision invisible to the gate the skill
|
|
123
|
+
* calls structural ("cannot be skipped by prose"). Verified with a control
|
|
124
|
+
* before the fix: numbered heading + a live question → `[]`; the identical
|
|
125
|
+
* question under a plain heading → caught. Its sibling `findDecisionPointGaps`
|
|
126
|
+
* failed CLOSED on the very same input — two defaults for one quantity.
|
|
127
|
+
*/
|
|
128
|
+
const SECTION_LABEL = String.raw`(?:\d+(?:\.\d+)*[a-z]?[.)]?\s+)?`;
|
|
129
|
+
function gateSectionHeadingRe(name) {
|
|
130
|
+
return new RegExp(String.raw`^##\s+${SECTION_LABEL}${name}\b[^\n]*$`, 'im');
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
export function findOpenQuestions(specBody) {
|
|
106
134
|
// \b…[^\n]*$ (not \s*$) so heading variants like "## Open questions (round 2)"
|
|
107
135
|
// or "## Open Questions & Decisions" are still recognized — a variant heading
|
|
108
136
|
// must not make the section invisible to the gate (reviewer finding, PR 2).
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
// SECTION_LABEL additionally tolerates a numbered prefix (see the builder).
|
|
138
|
+
const m = specBody.match(gateSectionHeadingRe('Open questions'));
|
|
139
|
+
// A genuinely ABSENT section still means nothing is parked on the user; that
|
|
140
|
+
// semantic is unchanged and separately tested. What changed is that a present
|
|
141
|
+
// section can no longer hide behind its own section number.
|
|
142
|
+
if (!m) return [];
|
|
111
143
|
const start = m.index + m[0].length;
|
|
112
144
|
const restAfter = specBody.slice(start);
|
|
113
145
|
const nextHeading = restAfter.search(/^##\s+/m);
|
|
@@ -144,7 +176,11 @@ export const GRANDFATHERED_SLUGS = [
|
|
|
144
176
|
|
|
145
177
|
export function findDecisionPointGaps(specBody, slug) {
|
|
146
178
|
if (slug && GRANDFATHERED_SLUGS.includes(slug)) return { ok: true };
|
|
147
|
-
|
|
179
|
+
// Same shared builder as findOpenQuestions — this gate already failed CLOSED
|
|
180
|
+
// on a numbered heading (correct direction), but it was refusing specs whose
|
|
181
|
+
// section was PRESENT and merely numbered, which is a false refusal rather
|
|
182
|
+
// than a safety property. Both gates now recognise the same heading shapes.
|
|
183
|
+
const m = specBody.match(gateSectionHeadingRe('Decision points touched'));
|
|
148
184
|
if (!m) return { ok: false, reason: 'missing-section' };
|
|
149
185
|
const start = m.index + m[0].length;
|
|
150
186
|
const restAfter = specBody.slice(start);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-07-27T22:23:55.031Z",
|
|
5
|
+
"instarVersion": "1.3.1019",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
A correction made earlier today to how design reviews decide they are finished now actually reaches
|
|
9
|
+
agents that are already running. It previously did not.
|
|
10
|
+
|
|
11
|
+
Tools like that review process exist both in what ships and as a copy on each running agent's disk.
|
|
12
|
+
The install step deliberately never overwrites an existing copy, in case an operator has customised
|
|
13
|
+
it, so an update reaches running agents only through a small dedicated delivery step. One already
|
|
14
|
+
existed for this file — but its "have I done this already?" check was keyed to an older change, so
|
|
15
|
+
once an agent had taken that one it quietly stopped delivering anything further to the same file.
|
|
16
|
+
|
|
17
|
+
The build guard that stops a switched-off feature from claiming a graduation condition it can
|
|
18
|
+
never check now covers a second kind of feature document it previously skipped.
|
|
19
|
+
|
|
20
|
+
Some features graduate alongside a bigger parent feature rather than on their own. The guard
|
|
21
|
+
treated those as out of scope. They are not: such a feature still states its own condition and
|
|
22
|
+
still names an address to read it from, so a missing address parks it for exactly the same
|
|
23
|
+
reason. Three feature documents sat outside the guard; all three are now covered.
|
|
24
|
+
|
|
25
|
+
## What to Tell Your User
|
|
26
|
+
|
|
27
|
+
Nothing changes in how your agent behaves day to day. A fix that had been shipped but could not
|
|
28
|
+
reach already-running agents now reaches them.
|
|
29
|
+
|
|
30
|
+
Nothing changes in how your agent behaves. A safety check that runs when the project is built
|
|
31
|
+
now looks at a few more files than it used to.
|
|
32
|
+
|
|
33
|
+
## Summary of New Capabilities
|
|
34
|
+
|
|
35
|
+
None. No endpoint, config key, or behaviour is added. This is a delivery step so an existing fix
|
|
36
|
+
arrives where it was always meant to go.
|
|
37
|
+
|
|
38
|
+
Agents that have customised the file keep their version untouched, agents that already have the
|
|
39
|
+
correction are unaffected, and a fresh install is unchanged.
|
|
40
|
+
|
|
41
|
+
None. No endpoint, config key, or behaviour is added. This widens the scope of an existing
|
|
42
|
+
build-time check and reports its two counts separately, so a future narrowing shows up as a
|
|
43
|
+
number dropping rather than as silence.
|
|
44
|
+
|
|
45
|
+
Widening it forces no new work on anyone. The guard already carries an escape hatch — a list
|
|
46
|
+
of accepted exceptions, each requiring a written reason, each deleted automatically once its
|
|
47
|
+
address starts working. A feature whose parent was genuinely abandoned needs a declaration,
|
|
48
|
+
not a fix.
|
|
49
|
+
|
|
50
|
+
## Evidence
|
|
51
|
+
|
|
52
|
+
The gap was confirmed on a real running agent rather than reasoned about: its copy carried the older
|
|
53
|
+
change's marker and did not carry the correction, so the update could never have arrived.
|
|
54
|
+
|
|
55
|
+
The delivery step was proved to work by simulating exactly that agent — one that already took the
|
|
56
|
+
earlier change — and then deliberately breaking the fix in the way that would reintroduce the
|
|
57
|
+
problem, which made precisely that test fail and no other.
|
|
58
|
+
|
|
59
|
+
Nothing was broken when this was written: all eight covered addresses currently work, and the
|
|
60
|
+
accepted-exception list is empty. This closes a hole in the guard rather than repairing a
|
|
61
|
+
fault.
|
|
62
|
+
|
|
63
|
+
The guard was proved to actually bite by breaking one of the newly-covered documents on
|
|
64
|
+
purpose — the check refused, and named which kind of document had failed — then restoring it
|
|
65
|
+
byte-for-byte and confirming it passed again. The tests were proved the same way, by narrowing
|
|
66
|
+
the check back and watching exactly the two new tests fail.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The convergence tag writer refuses to mark a design document "converged" while it
|
|
9
|
+
still has an unanswered question parked on a person. That check only recognised a
|
|
10
|
+
section titled exactly `Open questions`. Many documents number their sections —
|
|
11
|
+
`9. Open questions` — and for those the check found nothing, concluded there was
|
|
12
|
+
nothing to find, and let the document through.
|
|
13
|
+
|
|
14
|
+
The two halves of the same gate also disagreed with each other on identical input:
|
|
15
|
+
the sibling check covering decision points refused a numbered heading outright,
|
|
16
|
+
while the open-questions check waved it past. Both now recognise the same set of
|
|
17
|
+
heading shapes (plain, numbered, lettered, dotted, parenthesised, and with a
|
|
18
|
+
trailing variant such as `(round 2)`), and they share one matcher so a future fix
|
|
19
|
+
to one cannot silently miss the other.
|
|
20
|
+
|
|
21
|
+
What deliberately did NOT change: a document with genuinely no such section is
|
|
22
|
+
still treated as having nothing outstanding. Whether that should instead refuse is
|
|
23
|
+
a separate decision worth arguing on its own rather than folding in here.
|
|
24
|
+
|
|
25
|
+
## What to Tell Your User
|
|
26
|
+
|
|
27
|
+
Nothing you need to do. A safety check that decides whether a design document is
|
|
28
|
+
finished had a blind spot: if the document numbered its sections, an unanswered
|
|
29
|
+
question could slip past unnoticed. It now sees those documents too.
|
|
30
|
+
|
|
31
|
+
## Summary of New Capabilities
|
|
32
|
+
|
|
33
|
+
No new capability. This repairs a check that already existed and was quietly not
|
|
34
|
+
looking at part of what it was meant to cover.
|
|
35
|
+
|
|
36
|
+
## Evidence
|
|
37
|
+
|
|
38
|
+
The defect was reproduced with a control before any fix was written: a live,
|
|
39
|
+
unanswered question under a numbered heading returned "nothing outstanding", while
|
|
40
|
+
the identical question under a plain heading was caught.
|
|
41
|
+
|
|
42
|
+
Seven tests now cover the numbered, lettered, dotted, parenthesised and
|
|
43
|
+
variant-suffix headings plus the cross-check that both halves of the gate agree.
|
|
44
|
+
Reverting the fix makes exactly those seven fail; restoring it returns 50 passing
|
|
45
|
+
tests across all four suites that touch the changed file, with type-checking clean.
|
|
46
|
+
|
|
47
|
+
## Title
|
|
48
|
+
|
|
49
|
+
A section number could hide an unanswered question from the convergence gate
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Side effects — open-questions gate: numbered-heading recognition
|
|
2
|
+
|
|
3
|
+
## What this change can affect
|
|
4
|
+
|
|
5
|
+
`write-convergence-tag.mjs` is the structural gate for `/spec-converge`. Widening
|
|
6
|
+
heading recognition changes which specs the gate can SEE, so both directions were
|
|
7
|
+
checked rather than only the one being fixed.
|
|
8
|
+
|
|
9
|
+
## Newly-visible sections (the intended effect)
|
|
10
|
+
|
|
11
|
+
A spec whose `Open questions` section is numbered was previously invisible to the
|
|
12
|
+
gate; it is now parsed. **Consequence to state plainly: a spec that would have been
|
|
13
|
+
stamped before may now be REFUSED — correctly — because it carries a live
|
|
14
|
+
unresolved question that the gate could not previously see.** That is the point of
|
|
15
|
+
the fix, and it is a behaviour change for any such spec mid-flight.
|
|
16
|
+
|
|
17
|
+
## Not changed, deliberately
|
|
18
|
+
|
|
19
|
+
- A genuinely ABSENT `Open questions` section still yields "nothing parked on the
|
|
20
|
+
user". Whether an absent section should instead fail closed is a separate argued
|
|
21
|
+
decision; smuggling it in here would be a semantic change hiding inside a
|
|
22
|
+
matcher fix. Tracked, not silently taken.
|
|
23
|
+
- Resolution semantics are untouched: `*(none)*`, `(none)`, `None`, `N/A`,
|
|
24
|
+
blockquote commentary and horizontal rules still count as resolved, including
|
|
25
|
+
under a numbered heading (explicitly tested, so the fix cannot become
|
|
26
|
+
"refuse every numbered spec").
|
|
27
|
+
- `GRANDFATHERED_SLUGS` is untouched and remains empty.
|
|
28
|
+
|
|
29
|
+
## The decision-points gate
|
|
30
|
+
|
|
31
|
+
That sibling previously refused a numbered heading with `missing-section`. That is
|
|
32
|
+
a FALSE refusal — the section was present, merely numbered — so it now recognises
|
|
33
|
+
the same shapes. This makes the gate less likely to block a conforming spec; it
|
|
34
|
+
does not weaken it, because an actually-missing section still refuses.
|
|
35
|
+
|
|
36
|
+
## Blast radius and rollback
|
|
37
|
+
|
|
38
|
+
Two files: one script, one test file. No route, no config key, no persisted state,
|
|
39
|
+
no migration. Rollback is a revert.
|
|
40
|
+
|
|
41
|
+
## Honest limit
|
|
42
|
+
|
|
43
|
+
The matcher tolerates a bounded set of section-label shapes. An exotic heading
|
|
44
|
+
(e.g. a roman numeral, or an emoji prefix) would still be invisible, and the
|
|
45
|
+
underlying design remains "match a heading by name". A structurally stronger
|
|
46
|
+
answer — a declared anchor rather than a heading regex — was NOT attempted here
|
|
47
|
+
and is a larger change than this repair.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Side-effects review — rollout-evidence ratchet widened to `composed`
|
|
2
|
+
|
|
3
|
+
## What this changes
|
|
4
|
+
|
|
5
|
+
`scripts/lint-rollout-evidence-resolvable.js` guarded `rollout-disposition: active` only.
|
|
6
|
+
It now guards `active` AND `composed`. Guarded endpoint specs go from 5 to 8.
|
|
7
|
+
|
|
8
|
+
## Blast radius
|
|
9
|
+
|
|
10
|
+
The lint runs in the `npm run lint` chain, so it gates every commit and every CI run.
|
|
11
|
+
Widening it can, in principle, fail a build that previously passed.
|
|
12
|
+
|
|
13
|
+
**Measured, not assumed: it cannot do so today.** All eight guarded refs resolve; the lint
|
|
14
|
+
exits 0 with an EMPTY accepted-baseline. The three newly-covered specs are
|
|
15
|
+
`self-heal-gate.md` (`/feedback-factory/drain/status`), `context-wedge-detection-completeness.md`
|
|
16
|
+
(`/health`), and `slack-considered-acknowledgment-v1.md` (`/permissions/ambient-stats`).
|
|
17
|
+
Two were already transitively covered — one shares its ref with an active spec, one names a
|
|
18
|
+
path that trivially exists. Exactly one was genuinely unguarded.
|
|
19
|
+
|
|
20
|
+
## The failure mode this could introduce, and why it does not
|
|
21
|
+
|
|
22
|
+
The obvious risk of widening a guard is forcing unrelated work on whoever trips it next: a
|
|
23
|
+
`composed` spec whose owner feature was abandoned may legitimately carry a stale ref, and
|
|
24
|
+
such a person should not be conscripted into building a route they do not want.
|
|
25
|
+
|
|
26
|
+
They are not. Assertion C's shrink-only baseline is the escape hatch, and it was designed
|
|
27
|
+
before this change: an accepted entry requires a written reason and is auto-deleted the
|
|
28
|
+
moment its ref starts resolving. So the widened guard forces a DECLARATION, never a fix.
|
|
29
|
+
Entry needs evidence; exit happens by itself.
|
|
30
|
+
|
|
31
|
+
## What is NOT weakened
|
|
32
|
+
|
|
33
|
+
Assertions A (every guarded ref resolves unless accepted), B (baseline reasons must be
|
|
34
|
+
substantive), and C (baseline shrinks only) are unchanged. No threshold moved. The
|
|
35
|
+
refusal message now names which disposition fired, which is strictly more information.
|
|
36
|
+
|
|
37
|
+
## Verification
|
|
38
|
+
|
|
39
|
+
- Live run: `clean — 8 guarded endpoint spec(s) (5 active, 3 composed), 8 resolving, 0 accepted-unresolved`, exit 0.
|
|
40
|
+
- Falsified against the real subject: breaking the COMPOSED spec's ref → exit 1 with a
|
|
41
|
+
refusal naming `rollout-disposition:composed`. Spec restored byte-identical; exit 0 again.
|
|
42
|
+
- Falsified against the tests: narrowing the filter back to active-only → `2 failed | 7 passed`.
|
|
43
|
+
Restored → `9 passed`.
|
|
44
|
+
- Two pre-existing tests asserted the OLD summary wording and correctly broke. Their
|
|
45
|
+
PROPERTY — that the guard must report its denominator, since "clean" alone cannot be told
|
|
46
|
+
from "scanned nothing" — is preserved, with the pattern updated rather than the assertion
|
|
47
|
+
dropped.
|
|
48
|
+
|
|
49
|
+
## Reviewer's note
|
|
50
|
+
|
|
51
|
+
This is a correction to my own work from earlier the same day. The lint's header claimed a
|
|
52
|
+
sweep of "all 5 rollout-active specs" — accurate for its scope, and the scope was narrower
|
|
53
|
+
than the class it names. Recorded in the source comment rather than quietly widened, because
|
|
54
|
+
a guard that silently grew is indistinguishable from one that was always right.
|
package/upgrades/1.3.1017.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Upgrade Guide — vNEXT
|
|
2
|
-
|
|
3
|
-
<!-- assembled-by: assemble-next-md -->
|
|
4
|
-
<!-- bump: patch -->
|
|
5
|
-
|
|
6
|
-
## What Changed
|
|
7
|
-
|
|
8
|
-
A correction made earlier today to how design reviews decide they are finished now actually reaches
|
|
9
|
-
agents that are already running. It previously did not.
|
|
10
|
-
|
|
11
|
-
Tools like that review process exist both in what ships and as a copy on each running agent's disk.
|
|
12
|
-
The install step deliberately never overwrites an existing copy, in case an operator has customised
|
|
13
|
-
it, so an update reaches running agents only through a small dedicated delivery step. One already
|
|
14
|
-
existed for this file — but its "have I done this already?" check was keyed to an older change, so
|
|
15
|
-
once an agent had taken that one it quietly stopped delivering anything further to the same file.
|
|
16
|
-
|
|
17
|
-
## What to Tell Your User
|
|
18
|
-
|
|
19
|
-
Nothing changes in how your agent behaves day to day. A fix that had been shipped but could not
|
|
20
|
-
reach already-running agents now reaches them.
|
|
21
|
-
|
|
22
|
-
## Summary of New Capabilities
|
|
23
|
-
|
|
24
|
-
None. No endpoint, config key, or behaviour is added. This is a delivery step so an existing fix
|
|
25
|
-
arrives where it was always meant to go.
|
|
26
|
-
|
|
27
|
-
Agents that have customised the file keep their version untouched, agents that already have the
|
|
28
|
-
correction are unaffected, and a fresh install is unchanged.
|
|
29
|
-
|
|
30
|
-
## Evidence
|
|
31
|
-
|
|
32
|
-
The gap was confirmed on a real running agent rather than reasoned about: its copy carried the older
|
|
33
|
-
change's marker and did not carry the correction, so the update could never have arrived.
|
|
34
|
-
|
|
35
|
-
The delivery step was proved to work by simulating exactly that agent — one that already took the
|
|
36
|
-
earlier change — and then deliberately breaking the fix in the way that would reintroduce the
|
|
37
|
-
problem, which made precisely that test fail and no other.
|