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.
Files changed (31) hide show
  1. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  2. package/dist/core/PostUpdateMigrator.js +16 -1
  3. package/dist/core/PostUpdateMigrator.js.map +1 -1
  4. package/dist/data/standards-guard-index.json +1 -1
  5. package/dist/data/standards-guard-index.meta.json +2 -2
  6. package/dist/data/standards-registry.meta.json +1 -1
  7. package/dist/feedback-factory/execute/FeedbackExecutorService.d.ts.map +1 -1
  8. package/dist/feedback-factory/execute/FeedbackExecutorService.js +13 -3
  9. package/dist/feedback-factory/execute/FeedbackExecutorService.js.map +1 -1
  10. package/dist/feedback-factory/execute/reviewGate.d.ts +3 -2
  11. package/dist/feedback-factory/execute/reviewGate.d.ts.map +1 -1
  12. package/dist/feedback-factory/execute/reviewGate.js +3 -2
  13. package/dist/feedback-factory/execute/reviewGate.js.map +1 -1
  14. package/dist/monitoring/GreenPrAutoMerger.d.ts.map +1 -1
  15. package/dist/monitoring/GreenPrAutoMerger.js +11 -1
  16. package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
  17. package/dist/monitoring/MergeRunner.d.ts +4 -5
  18. package/dist/monitoring/MergeRunner.d.ts.map +1 -1
  19. package/dist/monitoring/MergeRunner.js +2 -2
  20. package/dist/monitoring/MergeRunner.js.map +1 -1
  21. package/dist/native/instar-desktop-host +0 -0
  22. package/dist/scaffold/templates.js +1 -1
  23. package/package.json +1 -1
  24. package/scripts/safe-merge.mjs +65 -67
  25. package/src/data/builtin-manifest.json +19 -19
  26. package/src/data/standards-guard-index.json +1 -1
  27. package/src/data/standards-guard-index.meta.json +2 -2
  28. package/src/data/standards-registry.meta.json +1 -1
  29. package/src/scaffold/templates.ts +1 -1
  30. package/upgrades/1.3.1622.md +21 -0
  31. package/upgrades/side-effects/feedback-ci-floor-merge.md +82 -0
@@ -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. Arms GitHub native auto-merge and returns immediately —
32
- * GitHub merges the instant every required check passes, enforcing
33
- * branch protection itself (no --admin bypass) and never timing out.
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
- // --auto (native GitHub auto-merge: GitHub enforces every required check
197
- // before merging) and --admin (bypass-then-re-impose) are contradictory
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
- 'native-auto-merge', 'auto-arm-unavailable-detection',
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, autoMergeArmed: 5 },
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 (classifyChecks never reads zero rows as green). Any
673
- * other unparseable, non-array or empty answer throws (retried, then
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 Error(`gh pr checks returned no checks${errNote}`);
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
- // --- Native auto-merge path (--auto): arm-and-return, no polling ----------
1182
- // GitHub's native auto-merge merges the PR the instant every REQUIRED check
1183
- // passes and the branch is mergeable — and it NEVER bypasses a check (unlike
1184
- // --admin). So the required-context re-imposition this script does manually
1185
- // for the --admin path is enforced by GitHub itself here. We only run the
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: refuse before arming (GitHub would
1192
- // otherwise merge at once when no check is required).
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
- if (noActionsChecksRan(headRuns)) refuseNoChecksRan(pr, pinnedHead);
1202
- const autoArgs = ['pr', 'merge', pr, '--repo', repo, args.method, '--auto'];
1203
- if (args.matchHeadCommit) autoArgs.push('--match-head-commit', pinnedHead);
1204
- if (args.deleteBranch) autoArgs.push('--delete-branch');
1205
- console.log(`safe-merge: arming native auto-merge for PR #${pr} (${args.method}, head ${pinnedHead.slice(0, 12)}) — GitHub will merge when all required checks pass...`);
1206
- const a = spawnSync('gh', autoArgs, { encoding: 'utf-8' });
1207
- process.stdout.write(a.stdout ?? '');
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
- result({ result: `refused:auto-arm-${cls}`, raw: cls });
1223
- console.error(`safe-merge: could not arm auto-merge (${cls}). Is "Allow auto-merge" enabled on the repo settings?`);
1224
- process.exit(1);
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
- if (after.autoMergeRequest) {
1235
- result({ result: 'auto-merge-armed', pr: Number(pr), head: pinnedHead });
1236
- console.log('safe-merge: auto-merge armed. GitHub will merge when required checks pass — no further action needed.');
1237
- process.exit(5);
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
- result({ result: 'error:auto-arm-unconfirmed', state: after.state });
1240
- console.error('safe-merge: gh reported success but auto-merge is not armed on re-read. NOT claiming success.');
1241
- process.exit(2);
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
- result({ result: 'error:auto-confirm-unreadable', detail: String(e.message).slice(0, 300) });
1244
- console.error('safe-merge: could not independently confirm auto-merge was armed. NOT claiming success.');
1245
- process.exit(2);
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
- // `--auto` (above) keeps today's behaviour. On the synchronous path, the
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-03T09:33:37.326Z",
5
- "instarVersion": "1.3.1621",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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": "21e0ea6311ec708c169366ccb1ee0fc26cd5bb047f6063a68aff2edb07178ff5",
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.1621",
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": "da21716275caefa7f4ca6007e41697a4ccb0429bb58765ba010f7934615f992b",
2
+ "sha256": "25dd2246cd8ba1e0f2bf4d157be9613a2c1d1994aed32d7e13f45b34d887bd8f",
3
3
  "registrySha256": "db01d9f92c6aa26ca903702896b41be53722982ed07d27633237bb254c214290",
4
- "packageVersion": "1.3.1621"
4
+ "packageVersion": "1.3.1622"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "db01d9f92c6aa26ca903702896b41be53722982ed07d27633237bb254c214290",
3
3
  "articleCount": 93,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1621"
5
+ "packageVersion": "1.3.1622"
6
6
  }
@@ -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\` / \`auto-merge-disabled\` / \`profile-unenforceable\` / \`deps-unavailable\` / \`publish-fork-unset\` / \`dry-run\` / \`no-source-repo\`).
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 }`.