instar 1.3.1621 → 1.3.1622
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/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +16 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/feedback-factory/execute/FeedbackExecutorService.d.ts.map +1 -1
- package/dist/feedback-factory/execute/FeedbackExecutorService.js +13 -3
- package/dist/feedback-factory/execute/FeedbackExecutorService.js.map +1 -1
- package/dist/feedback-factory/execute/reviewGate.d.ts +3 -2
- package/dist/feedback-factory/execute/reviewGate.d.ts.map +1 -1
- package/dist/feedback-factory/execute/reviewGate.js +3 -2
- package/dist/feedback-factory/execute/reviewGate.js.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.d.ts.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.js +11 -1
- package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
- package/dist/monitoring/MergeRunner.d.ts +4 -5
- package/dist/monitoring/MergeRunner.d.ts.map +1 -1
- package/dist/monitoring/MergeRunner.js +2 -2
- package/dist/monitoring/MergeRunner.js.map +1 -1
- package/dist/native/instar-desktop-host +0 -0
- package/dist/scaffold/templates.js +1 -1
- package/package.json +1 -1
- package/scripts/safe-merge.mjs +65 -67
- package/src/data/builtin-manifest.json +19 -19
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/scaffold/templates.ts +1 -1
- package/upgrades/1.3.1622.md +21 -0
- package/upgrades/side-effects/feedback-ci-floor-merge.md +82 -0
package/scripts/safe-merge.mjs
CHANGED
|
@@ -28,11 +28,9 @@
|
|
|
28
28
|
* Usage: node scripts/safe-merge.mjs <PR#> [--auto|--admin] [--squash|--merge|--rebase]
|
|
29
29
|
* [--repo <owner/name>] [--delete-branch] [--deadline-ms <n>]
|
|
30
30
|
*
|
|
31
|
-
* --auto PREFERRED.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* Requires "Allow auto-merge" enabled on the repo. Exit 5 = armed,
|
|
35
|
-
* exit 0 = merged immediately (checks were already green).
|
|
31
|
+
* --auto PREFERRED. Defers while CI is pending, then synchronously merges
|
|
32
|
+
* the verified head once the full code-pinned CI floor is green.
|
|
33
|
+
* It never arms GitHub's weaker native auto-merge policy.
|
|
36
34
|
* --admin Legacy synchronous path: polls for green, re-imposes the required
|
|
37
35
|
* contexts that --admin would bypass, then merges. Use only when the
|
|
38
36
|
* repo has auto-merge disabled.
|
|
@@ -193,10 +191,8 @@ export function parseArgs(argv) {
|
|
|
193
191
|
throw new UsageError(`unknown flag: ${a} (strict argv — see --capabilities for the contract)`);
|
|
194
192
|
}
|
|
195
193
|
if (!out.capabilities && out.pr === null) throw new UsageError('a PR number is required');
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
// strategies — refuse the incoherent combo rather than silently pick one.
|
|
199
|
-
if (out.auto && out.admin) throw new UsageError('--auto and --admin are mutually exclusive: --auto relies on GitHub native required-check enforcement, --admin bypasses it');
|
|
194
|
+
// These are distinct merge floors, so the caller must choose one.
|
|
195
|
+
if (out.auto && out.admin) throw new UsageError('--auto and --admin are mutually exclusive merge strategies');
|
|
200
196
|
return out;
|
|
201
197
|
}
|
|
202
198
|
|
|
@@ -209,12 +205,12 @@ export function capabilities() {
|
|
|
209
205
|
'strict-argv', 'repo-param', 'json-checks', 'head-pinning',
|
|
210
206
|
'required-contexts-cross-check', 'producer-binding', 'floor',
|
|
211
207
|
'reviews-required-refusal', 'classified-exits', 'delete-branch', 'deadline-ms',
|
|
212
|
-
'
|
|
208
|
+
'ci-floor-sync-merge', 'pending-check-deferral',
|
|
213
209
|
'approval-required-refusal', 'request-review-from', 'local-floor', 'attempt-token',
|
|
214
210
|
'non-main-base-refusal',
|
|
215
211
|
],
|
|
216
212
|
floorModes: [...MERGE_FLOOR_MODES],
|
|
217
|
-
exitCodes: { merged: 0, refused: 1, usageOrError: 2, alreadyMerged: 3, closed: 4
|
|
213
|
+
exitCodes: { merged: 0, refused: 1, usageOrError: 2, alreadyMerged: 3, closed: 4 },
|
|
218
214
|
};
|
|
219
215
|
}
|
|
220
216
|
|
|
@@ -665,13 +661,13 @@ function fetchChecksRows(pr, repo) {
|
|
|
665
661
|
|
|
666
662
|
/** gh's answer for a head GitHub has not registered any check run on yet (e.g. right after a push). */
|
|
667
663
|
const NO_CHECKS_REPORTED_RE = /no checks reported/i;
|
|
664
|
+
export class NoChecksReportedError extends Error {}
|
|
668
665
|
|
|
669
666
|
/**
|
|
670
667
|
* Parse `gh pr checks --json` output. An empty stdout whose stderr says "no checks
|
|
671
668
|
* reported" (seen right after a push, PRs 2296/2314) is ZERO rows — not settled,
|
|
672
|
-
* so the caller keeps waiting
|
|
673
|
-
*
|
|
674
|
-
* contexts-unverifiable), with gh's stderr in the message so it is diagnosable.
|
|
669
|
+
* so the caller keeps waiting. Any other unparseable, non-array or empty answer
|
|
670
|
+
* throws (retried, then contexts-unverifiable).
|
|
675
671
|
*/
|
|
676
672
|
export function parseChecksRows(stdout, stderr = '') {
|
|
677
673
|
const err = String(stderr || '').trim().slice(0, 200);
|
|
@@ -684,7 +680,7 @@ export function parseChecksRows(stdout, stderr = '') {
|
|
|
684
680
|
throw new Error(`gh pr checks returned unparseable JSON: ${String(stdout || '').slice(0, 200)}${errNote}`);
|
|
685
681
|
}
|
|
686
682
|
if (!Array.isArray(rows)) throw new Error(`gh pr checks returned a non-array: ${String(stdout || '').slice(0, 200)}${errNote}`);
|
|
687
|
-
if (rows.length === 0) throw new
|
|
683
|
+
if (rows.length === 0) throw new NoChecksReportedError(`gh pr checks returned no checks${errNote}`);
|
|
688
684
|
return rows;
|
|
689
685
|
}
|
|
690
686
|
|
|
@@ -1178,18 +1174,14 @@ async function main() {
|
|
|
1178
1174
|
process.exit(1);
|
|
1179
1175
|
}
|
|
1180
1176
|
|
|
1181
|
-
// ---
|
|
1182
|
-
// GitHub
|
|
1183
|
-
//
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1186
|
-
// cheap pre-flight above (open, not draft, head not moved), arm auto-merge,
|
|
1187
|
-
// confirm it's armed, and exit. No deadline, so it can't time out the way a
|
|
1188
|
-
// foreground/background poller does (the failure mode that wedged hot-branch
|
|
1189
|
-
// merges: the watcher is killed before slow CI finishes).
|
|
1177
|
+
// --- CI-floor merge path (--auto): defer until green, then merge now ------
|
|
1178
|
+
// GitHub enforces only the contexts configured in branch rules. On this repo
|
|
1179
|
+
// that can be just instar/local-verify, so --auto would merge while the CI
|
|
1180
|
+
// matrix is still queued. Check the full code-pinned Actions floor before
|
|
1181
|
+
// merging. A pending check is a deferral: the caller retries on its next tick.
|
|
1190
1182
|
if (args.auto) {
|
|
1191
|
-
// Zero Actions checks is never green
|
|
1192
|
-
//
|
|
1183
|
+
// Zero Actions checks is never green. A queued run may not yet appear,
|
|
1184
|
+
// so classify pending first and refuse only once the rows have settled.
|
|
1193
1185
|
let headRuns;
|
|
1194
1186
|
try {
|
|
1195
1187
|
headRuns = fetchCheckRunsWithProducers(repo, pinnedHead);
|
|
@@ -1198,56 +1190,62 @@ async function main() {
|
|
|
1198
1190
|
console.error(`safe-merge: REFUSING — could not read the head's check runs: ${e.message}`);
|
|
1199
1191
|
process.exit(1);
|
|
1200
1192
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
process.stderr.write(a.stderr ?? '');
|
|
1209
|
-
if (a.status !== 0 || a.signal) {
|
|
1210
|
-
const cls = classifyMergeFailure(a.stderr, a.status, a.signal);
|
|
1211
|
-
if (cls === 'already-merged') { result({ result: 'already-merged', pr: Number(pr) }); process.exit(3); }
|
|
1212
|
-
if (cls === 'closed') { result({ result: 'closed', pr: Number(pr) }); process.exit(4); }
|
|
1213
|
-
// Distinguish "auto-merge is disabled on the repo" (a PERMANENT condition →
|
|
1214
|
-
// terminal-non-ladder for the orchestrator) from a generic transient gh
|
|
1215
|
-
// failure. The discriminator is a REAL stderr-signature match, not the
|
|
1216
|
-
// human-only console.error hint below.
|
|
1217
|
-
if (isAutoMergeUnavailable(a.stderr)) {
|
|
1218
|
-
result({ result: 'refused:auto-arm-unavailable', raw: cls });
|
|
1219
|
-
console.error('safe-merge: could not arm auto-merge — "Allow auto-merge" appears DISABLED on the repo settings. Enable it, or run with --admin.');
|
|
1193
|
+
let checksState;
|
|
1194
|
+
let evaluation;
|
|
1195
|
+
try {
|
|
1196
|
+
checksState = classifyChecks(fetchChecksRows(pr, repo));
|
|
1197
|
+
if (!checksState.settled) {
|
|
1198
|
+
result({ result: 'deferred:checks-pending', pr: Number(pr), pending: checksState.pending.slice(0, 20), noChecks: checksState.noChecks });
|
|
1199
|
+
console.error('safe-merge: CI checks are still pending; merge deferred.');
|
|
1220
1200
|
process.exit(1);
|
|
1221
1201
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
// Independent confirmation (B10: never trust the exit code alone). If the
|
|
1227
|
-
// checks were ALREADY green, GitHub merges immediately and we report merged.
|
|
1228
|
-
try {
|
|
1229
|
-
const after = ghJson(['pr', 'view', pr, '--repo', repo, '--json', 'state,mergedAt,autoMergeRequest']);
|
|
1230
|
-
if (after.state === 'MERGED' || after.mergedAt) {
|
|
1231
|
-
result({ result: 'merged', pr: Number(pr), head: pinnedHead });
|
|
1232
|
-
process.exit(0);
|
|
1202
|
+
if (checksState.failed.length > 0) {
|
|
1203
|
+
result({ result: 'refused:red-checks', failed: checksState.failed.slice(0, 20) });
|
|
1204
|
+
console.error('safe-merge: REFUSING — a check is red; merge not attempted.');
|
|
1205
|
+
process.exit(1);
|
|
1233
1206
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1207
|
+
// An external check can pass before Actions registers any rows. Treat
|
|
1208
|
+
// absent Actions/E2E as pending; the executor's three-hour deadline
|
|
1209
|
+
// makes a missing workflow a bounded, eventual refusal.
|
|
1210
|
+
if (noActionsChecksRan(headRuns) || !checksState.sawE2e) {
|
|
1211
|
+
result({ result: 'deferred:checks-pending', pr: Number(pr), pending: ['Actions or E2E not registered'] });
|
|
1212
|
+
console.error('safe-merge: Actions or E2E has not registered; merge deferred.');
|
|
1213
|
+
process.exit(1);
|
|
1238
1214
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1215
|
+
if (!checksState.e2ePassed) {
|
|
1216
|
+
result({ result: 'refused:e2e-missing-or-red' });
|
|
1217
|
+
console.error('safe-merge: REFUSING — E2E did not pass; merge not attempted.');
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
}
|
|
1220
|
+
const rulesetContexts = fetchRulesetContexts(repo, view.baseRefName || 'main');
|
|
1221
|
+
const commitStatuses = rulesetContexts.includes(LOCAL_VERIFY_CONTEXT) ? fetchCommitStatuses(repo, pinnedHead) : [];
|
|
1222
|
+
evaluation = evaluateRequiredContexts({
|
|
1223
|
+
protectionContexts: [], rulesetContexts, floor: ACTIONS_FLOOR_CONTEXTS,
|
|
1224
|
+
extraFloor: args.extraFloor, checkRuns: headRuns, commitStatuses,
|
|
1225
|
+
});
|
|
1242
1226
|
} catch (e) {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1227
|
+
if (e instanceof NoChecksReportedError) {
|
|
1228
|
+
result({ result: 'deferred:checks-pending', pr: Number(pr), noChecks: true });
|
|
1229
|
+
console.error('safe-merge: no CI checks have registered; merge deferred.');
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
}
|
|
1232
|
+
result({ result: 'refused:contexts-unverifiable', detail: String(e.message).slice(0, 300) });
|
|
1233
|
+
console.error(`safe-merge: REFUSING — could not verify the CI floor: ${e.message}`);
|
|
1234
|
+
process.exit(1);
|
|
1235
|
+
}
|
|
1236
|
+
if (!evaluation.ok) {
|
|
1237
|
+
const wrongProducer = evaluation.problems.some(problem => problem.includes('WRONG producer'));
|
|
1238
|
+
result({ result: wrongProducer ? 'refused:contexts' : 'deferred:checks-pending', problems: evaluation.problems.slice(0, 20) });
|
|
1239
|
+
console.error(`safe-merge: ${wrongProducer ? 'REFUSING' : 'deferring'} — code-pinned CI floor is not green; merge not attempted.`);
|
|
1240
|
+
process.exit(1);
|
|
1246
1241
|
}
|
|
1242
|
+
// Never leave a future merge armed: GitHub's required contexts can be
|
|
1243
|
+
// weaker than the floor just checked, and a rerun could turn red later.
|
|
1244
|
+
mergeAndConfirm({ repo, pr, view, args, pinnedHead, admin: false });
|
|
1247
1245
|
}
|
|
1248
1246
|
|
|
1249
1247
|
// --- Floor selection (local-verify-merge-authority §3) --------------------
|
|
1250
|
-
//
|
|
1248
|
+
// On the synchronous path without `--auto`, the
|
|
1251
1249
|
// `local` floor replaces the three Actions waits and the contexts floor with
|
|
1252
1250
|
// the §3 predicate; it never falls back to the Actions floor on its own.
|
|
1253
1251
|
{
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-11-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-11-03T10:37:50.201Z",
|
|
5
|
+
"instarVersion": "1.3.1622",
|
|
6
6
|
"entryCount": 206,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"domain": "identity",
|
|
12
12
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
13
13
|
"installedPath": ".instar/hooks/instar/session-start.sh",
|
|
14
|
-
"contentHash": "
|
|
14
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
15
15
|
"since": "2025-01-01"
|
|
16
16
|
},
|
|
17
17
|
"hook:dangerous-command-guard": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"domain": "safety",
|
|
21
21
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
22
22
|
"installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
|
|
23
|
-
"contentHash": "
|
|
23
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
24
24
|
"since": "2025-01-01"
|
|
25
25
|
},
|
|
26
26
|
"hook:grounding-before-messaging": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"domain": "safety",
|
|
30
30
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
31
31
|
"installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
|
|
32
|
-
"contentHash": "
|
|
32
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
33
33
|
"since": "2025-01-01"
|
|
34
34
|
},
|
|
35
35
|
"hook:compaction-recovery": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"domain": "identity",
|
|
39
39
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
40
40
|
"installedPath": ".instar/hooks/instar/compaction-recovery.sh",
|
|
41
|
-
"contentHash": "
|
|
41
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
42
42
|
"since": "2025-01-01"
|
|
43
43
|
},
|
|
44
44
|
"hook:external-operation-gate": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"domain": "safety",
|
|
48
48
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
49
49
|
"installedPath": ".instar/hooks/instar/external-operation-gate.js",
|
|
50
|
-
"contentHash": "
|
|
50
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
51
51
|
"since": "2025-01-01"
|
|
52
52
|
},
|
|
53
53
|
"hook:deferral-detector": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"domain": "safety",
|
|
57
57
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
58
58
|
"installedPath": ".instar/hooks/instar/deferral-detector.js",
|
|
59
|
-
"contentHash": "
|
|
59
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
60
60
|
"since": "2025-01-01"
|
|
61
61
|
},
|
|
62
62
|
"hook:self-stop-guard": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"domain": "coherence",
|
|
66
66
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
67
67
|
"installedPath": ".instar/hooks/instar/self-stop-guard.js",
|
|
68
|
-
"contentHash": "
|
|
68
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
69
69
|
"since": "2025-01-01"
|
|
70
70
|
},
|
|
71
71
|
"hook:post-action-reflection": {
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"domain": "evolution",
|
|
75
75
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
76
76
|
"installedPath": ".instar/hooks/instar/post-action-reflection.js",
|
|
77
|
-
"contentHash": "
|
|
77
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
78
78
|
"since": "2025-01-01"
|
|
79
79
|
},
|
|
80
80
|
"hook:external-communication-guard": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"domain": "safety",
|
|
84
84
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
85
85
|
"installedPath": ".instar/hooks/instar/external-communication-guard.js",
|
|
86
|
-
"contentHash": "
|
|
86
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
87
87
|
"since": "2025-01-01"
|
|
88
88
|
},
|
|
89
89
|
"hook:telegram-origin-guard": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"domain": "coherence",
|
|
102
102
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
103
103
|
"installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
|
|
104
|
-
"contentHash": "
|
|
104
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
105
105
|
"since": "2025-01-01"
|
|
106
106
|
},
|
|
107
107
|
"hook:scope-coherence-checkpoint": {
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"domain": "coherence",
|
|
111
111
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
112
112
|
"installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
|
|
113
|
-
"contentHash": "
|
|
113
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
114
114
|
"since": "2025-01-01"
|
|
115
115
|
},
|
|
116
116
|
"hook:free-text-guard": {
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"domain": "safety",
|
|
120
120
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
121
121
|
"installedPath": ".instar/hooks/instar/free-text-guard.sh",
|
|
122
|
-
"contentHash": "
|
|
122
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
123
123
|
"since": "2025-01-01"
|
|
124
124
|
},
|
|
125
125
|
"hook:claim-intercept": {
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"domain": "coherence",
|
|
129
129
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
130
130
|
"installedPath": ".instar/hooks/instar/claim-intercept.js",
|
|
131
|
-
"contentHash": "
|
|
131
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
132
132
|
"since": "2025-01-01"
|
|
133
133
|
},
|
|
134
134
|
"hook:claim-intercept-response": {
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"domain": "coherence",
|
|
138
138
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
139
139
|
"installedPath": ".instar/hooks/instar/claim-intercept-response.js",
|
|
140
|
-
"contentHash": "
|
|
140
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
141
141
|
"since": "2025-01-01"
|
|
142
142
|
},
|
|
143
143
|
"hook:stop-gate-router": {
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"domain": "safety",
|
|
147
147
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
148
148
|
"installedPath": ".instar/hooks/instar/stop-gate-router.js",
|
|
149
|
-
"contentHash": "
|
|
149
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
150
150
|
"since": "2025-01-01"
|
|
151
151
|
},
|
|
152
152
|
"hook:auto-approve-permissions": {
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"domain": "safety",
|
|
156
156
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
157
157
|
"installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
|
|
158
|
-
"contentHash": "
|
|
158
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
159
159
|
"since": "2025-01-01"
|
|
160
160
|
},
|
|
161
161
|
"job:health-check": {
|
|
@@ -1595,7 +1595,7 @@
|
|
|
1595
1595
|
"type": "subsystem",
|
|
1596
1596
|
"domain": "updates",
|
|
1597
1597
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
1598
|
-
"contentHash": "
|
|
1598
|
+
"contentHash": "765398e7aeb5d62739d45738bc73edb15e35308aac67b4871e401368af274de9",
|
|
1599
1599
|
"since": "2025-01-01"
|
|
1600
1600
|
},
|
|
1601
1601
|
"subsystem:scheduler": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "db01d9f92c6aa26ca903702896b41be53722982ed07d27633237bb254c214290",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1622",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/audits/phase-b/f10-triage.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "25dd2246cd8ba1e0f2bf4d157be9613a2c1d1994aed32d7e13f45b34d887bd8f",
|
|
3
3
|
"registrySha256": "db01d9f92c6aa26ca903702896b41be53722982ed07d27633237bb254c214290",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1622"
|
|
5
5
|
}
|
|
@@ -1170,7 +1170,7 @@ ${READINESS_STALL_NOTICE_BULLET}
|
|
|
1170
1170
|
- **When to use** (PROACTIVE): "what are we working on from feedback?" → the queue; "why was this report ignored / held?" → the summary's reasons (and the item's row in the queue/summary); \`authority: awaiting-approval\` → send the operator to the dashboard Feedback Drain tab, Triage section, for the one PIN tap. Turning ignores live goes through \`POST /feedback-factory/triage/plan\` (\`{"action":"ignore-live"}\`) and the operator's PIN on the dashboard — never a chat confirmation.
|
|
1171
1171
|
|
|
1172
1172
|
**Feedback executor (operated feedback factory)** — The executor takes the highest-ranked **work** items and turns each into a pull request. Each attempt runs a Claude Code build session locked in an OS sandbox (no network, no credentials, no reads outside its own copy of the code), proven by a must-fail/must-succeed canary before every attempt; the session reproduces the problem with a failing test and fixes it (or drafts a spec when the item needs a design). Trusted code then re-checks the fix in the sandbox (fails before, passes after, lint and related tests pass), refuses changes to build tooling or anything that looks like a secret, and pushes from its own clone to a FORK (\`feedbackFactory.execute.publishRepo\`, \`owner/name\`), opening a cross-repository PR with the \`hold\` label — CI on a fork PR gets no repository secrets, and without a fork (or with the canonical repository itself) nothing is published. It merges ONLY the exact version the repository owner approved on GitHub (\`safe-merge --auto --match-head-commit\`), and refuses to run while this agent could itself act as that approver unless the operator accepted that once with the dashboard PIN. A merged fix is verified only after it ships in a release and the reports stay quiet for 30 days. Dev-gated (\`feedbackFactory.execute\`), dry-run until \`feedbackFactory.execute.dryRun\` is false; 503 without a source checkout.
|
|
1173
|
-
- Status: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/feedback-factory/execute/status\` → \`{ available, reason, dryRun, counts, live, openPrs, startsToday, limits, canary, deps, attempts }\` (\`reason\`: \`approver-not-independent\` / \`
|
|
1173
|
+
- Status: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/feedback-factory/execute/status\` → \`{ available, reason, dryRun, counts, live, openPrs, startsToday, limits, canary, deps, attempts }\` (\`reason\`: \`approver-not-independent\` / \`profile-unenforceable\` / \`deps-unavailable\` / \`publish-fork-unset\` / \`dry-run\` / \`no-source-repo\`). Native GitHub auto-merge need not be enabled; the reviewed head merges only after the complete CI floor passes.
|
|
1174
1174
|
- Operator instructions given in conversation: stop an item's attempts (also disarms an armed merge) \`curl -X POST -H "Authorization: Bearer $AUTH" -H 'X-Instar-Request: 1' -H 'Content-Type: application/json' http://localhost:${port}/feedback-factory/execute/stop -d '{"initiativeId":"..."}'\`; let a parked item be tried again \`.../feedback-factory/execute/release\` with the same body; withdraw the approver acceptance \`.../feedback-factory/execute/revoke-acceptance\` (no body).
|
|
1175
1175
|
- **When to use** (PROACTIVE): "is anything being fixed from feedback?" → the status and the queue's \`executionState\`/\`prLink\`; \`reason: approver-not-independent\` → send the operator to the dashboard Feedback Drain tab, Triage section, to accept or decline with the PIN (\`POST /feedback-factory/triage/plan\` \`{"action":"accept-approver-dependence"}\` renders it); a fix held as \`needs-review-secret-shape\` is published only through the same PIN plan/commit (\`{"action":"publish-secret-shape","attemptId":"..."}\`) — never a chat confirmation. You never merge an executor PR yourself.
|
|
1176
1176
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Feedback fixes and the green PR watcher now wait for the complete, producer-bound GitHub CI floor before merging. The merge command targets the exact verified head immediately after that check; it no longer leaves native auto-merge armed under a weaker repository rule. Queued or not-yet-registered checks make the feedback executor retry without spending its refusal allowance. That wait has a durable three-hour limit per attempt, followed by the existing one-hour retry path. The executor no longer requires native GitHub auto-merge to be enabled.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
A feedback PR stays open while CI is queued. When the full suite is green, Echo merges the reviewed commit. If checks never arrive, the executor eventually holds the work and reports the merge problem instead of calling a missing suite green.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- Feedback pull requests wait for all relevant GitHub checks and merge only the reviewed head after those checks pass.
|
|
17
|
+
- Queued checks are retried for up to three hours per attempt; the executor allows its existing retry before reporting a merge hold.
|
|
18
|
+
|
|
19
|
+
## Evidence
|
|
20
|
+
|
|
21
|
+
Focused unit, integration, and E2E tests cover pending checks, a Vercel-first registration window, the full producer-bound floor, head-pinned merging, and the bounded executor retry.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Side-Effects Review — Feedback CI floor merge
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `feedback-ci-floor-merge`
|
|
4
|
+
**Date:** 2026-11-03
|
|
5
|
+
**Author:** Echo
|
|
6
|
+
**Second-pass reviewer:** feedback_gate_review
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`scripts/safe-merge.mjs` now defers its `--auto` route until the full producer-bound Actions floor is green and then calls the immediate head-pinned merge. `FeedbackExecutorService` retries pending checks under a durable deadline and removes the obsolete native auto-merge prerequisite. The CLAUDE.md template and migration both explain the revised behavior. `GreenPrAutoMerger` treats the same deferral as backpressure, not a failed ladder attempt. Tests cover all three layers. The site delivery-checks guide explains the waiting states and restores the documentation coverage floor after the repository's recent class growth.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `safe-merge --auto` — modify — pending, red, missing, and green CI classification before a merge.
|
|
15
|
+
- `FeedbackExecutorService` — modify — pending retry and three-hour timeout.
|
|
16
|
+
- `FeedbackExecutorService.refreshAvailability` — modify — native auto-merge setting no longer gates an immediate CI-floor merge.
|
|
17
|
+
- `GreenPrAutoMerger.recordOutcome` — modify — pending does not consume ladder attempts.
|
|
18
|
+
|
|
19
|
+
## 1. Over-block
|
|
20
|
+
|
|
21
|
+
A legitimate PR whose GitHub Actions workflow is delayed more than three hours spends a merge refusal and may eventually be held. It remains recoverable through the existing retry and hold path. The executor no longer refuses solely because native auto-merge is disabled; it uses the immediate CI-floor merge path. Wrong-producer checks refuse even if a legitimate run later arrives; the next retry can pass after the correct run appears.
|
|
22
|
+
|
|
23
|
+
## 2. Under-block
|
|
24
|
+
|
|
25
|
+
A check rerun may turn red after the final read and before the merge command. GitHub enforces its configured contexts during the command, but its configured floor can be weaker. The head pin prevents a new commit from merging; the check-state race remains a short window. No durable native auto-merge request is left armed.
|
|
26
|
+
|
|
27
|
+
## 3. Level-of-abstraction fit
|
|
28
|
+
|
|
29
|
+
The merger owns the final CI predicate and the act-time head pin. The executor owns its durable retry clock. The watcher owns its ladder classification. Each decision stays at the component that has its required state.
|
|
30
|
+
|
|
31
|
+
## 4. Signal vs authority compliance
|
|
32
|
+
|
|
33
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
34
|
+
|
|
35
|
+
- [x] No — the CI floor is a deterministic safety invariant on an irreversible merge, with producer-bound source checks; pending is a signal to the existing retry authority.
|
|
36
|
+
|
|
37
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
38
|
+
|
|
39
|
+
The merge floor is an enumerable safety invariant: every named CI context from the pinned producer must be successful for the exact head. The retry deadline is a bounded operational policy, not a competing-signals judgment.
|
|
40
|
+
|
|
41
|
+
## 5. Interactions
|
|
42
|
+
|
|
43
|
+
The green PR watcher still recognizes historical armed requests and reconciles them. The new path produces no new armed requests. Feedback hold-label removal still precedes the safe-merge call; a pending result leaves the PR open and the watcher also defers while CI is pending.
|
|
44
|
+
|
|
45
|
+
## 6. External surfaces
|
|
46
|
+
|
|
47
|
+
`safe-merge --capabilities` now advertises `ci-floor-sync-merge` and `pending-check-deferral`. `--auto` still parses for existing callers, but its action is a synchronous verified merge. The result line returns `deferred:checks-pending` for queued or not-yet-registered checks.
|
|
48
|
+
|
|
49
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
50
|
+
|
|
51
|
+
The feedback row says why it is waiting. No operator action is requested while CI is merely queued. A timed-out feedback executor check goes through the existing merge-unavailable hold with one listed item. The green PR watcher retries pending checks without an executor-style hold deadline.
|
|
52
|
+
|
|
53
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
54
|
+
|
|
55
|
+
The head SHA and CI results are read from GitHub, common to all machines. The feedback row's deadline is in the existing execution store and follows its owner lease. The watcher uses its existing fenced lease and state ledger. No machine-local-only authority is added.
|
|
56
|
+
|
|
57
|
+
## 8. Rollback cost
|
|
58
|
+
|
|
59
|
+
No persistent schema migration is involved. Rolling back the script to its previous `--auto` behavior would reopen the weaker-floor merge risk for both the executor and the watcher. A safe rollback must first disable both autonomous merge paths or replace them with an immediate merge path that checks the full CI floor. Pending executor rows remain readable by an old binary, but retrying them with that binary is unsafe until its merge path is disabled.
|
|
60
|
+
|
|
61
|
+
## Conclusion
|
|
62
|
+
|
|
63
|
+
The change prevents merges based on a weaker GitHub rule and bounds delayed CI registration. The remaining read-to-merge check-state race is documented above.
|
|
64
|
+
|
|
65
|
+
## Second-pass review (if required)
|
|
66
|
+
|
|
67
|
+
**Reviewer:** feedback_gate_review
|
|
68
|
+
|
|
69
|
+
Concur with the review. The reviewer found and cleared the durable auto-arm, CI-registration, head-of-line, and rollback-description gaps after revisions. A later release-summary wording concern was corrected to say the three-hour window is per attempt and a retry precedes a hold; the reviewer reconfirmed concurrence. The final diff requires its exact-head tests and CI.
|
|
70
|
+
|
|
71
|
+
## Evidence pointers
|
|
72
|
+
|
|
73
|
+
- `tests/integration/safe-merge-no-checks-ran.test.ts`
|
|
74
|
+
- `tests/unit/feedback-factory/execute-service.test.ts`
|
|
75
|
+
- `tests/unit/green-pr-automerger.test.ts`
|
|
76
|
+
- `tests/e2e/feedback-execute-lifecycle.test.ts`
|
|
77
|
+
- `site/src/content/docs/features/feedback-delivery-checks.md`
|
|
78
|
+
- `upgrades/next/feedback-ci-floor-merge.md`
|
|
79
|
+
|
|
80
|
+
## Class-Closure Declaration (display-only mirror)
|
|
81
|
+
|
|
82
|
+
`defectClass: claim-vs-evidence`, `closure: guard`, `guardEvidence: { enforcementType: gate, citation: scripts/safe-merge.mjs, howCaught: the complete producer-bound CI floor is read before the head-pinned merge; pending checks return without a merge command }`.
|