fix: address second round of code-review findings

actions.js:
- Add assertValidRepo/assertValidIssueNumber guards at the top of all
  action handlers (applyClaim, applySync, applyValidate, applyPublish,
  applyReview, applyDecompose, applyUnblock) for fast-fail validation
- applyValidate: fix status transition — set 'validated' unconditionally
  when ok=true instead of preserving 'blocked' (was inconsistent with
  projectState becoming 'ready')

gh-api.js:
- runGh: preserve GITHUB_TOKEN by default; only delete when caller
  explicitly sets options.stripGithubToken=true (was deleting by
  default, breaking CI)

parsing.js:
- extractCoordinationState: throw SyntaxError on malformed JSON instead
  of silently returning null — lets callers distinguish bad JSON from
  absent marker
- normalizeBodyForComparison: fix regex to match JSON-quoted form
  "lastSyncAt": ... instead of bare lastSyncAt: ...

policy.js:
- loadPolicy: validate that parsed JSON is a plain object before
  spreading; coerce nested fields (labels, review, validation,
  branchModel, project, fieldNames) to objects before merging

state.js:
- assertIssueClaimable: block re-claim on status alone (not status AND
  owner) to prevent {status:'claimed', owner:null} bypass; use
  state.owner || 'unknown' in error message
- getCoordinationState: catch SyntaxError from extractCoordinationState,
  log warning to stderr, fall back to default state

tests/lib:
- Update malformed-JSON test to expect SyntaxError throw instead of null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Victor Casado
2026-06-11 14:25:58 -04:00
parent d4486a7a29
commit 33f2219307
6 changed files with 57 additions and 32 deletions
+5 -3
View File
@@ -7,7 +7,7 @@ function escapeRegExp(str) {
}
function normalizeBodyForComparison(body) {
return (body || '').replace(/lastSyncAt:\s*[^\n]+/g, 'lastSyncAt: NORMALIZED');
return (body || '').replace(/"lastSyncAt"\s*:\s*[^,\}\n]+/g, '"lastSyncAt": NORMALIZED');
}
function extractCoordinationState(body, policy = DEFAULT_POLICY) {
@@ -27,8 +27,10 @@ function extractCoordinationState(body, policy = DEFAULT_POLICY) {
try {
const parsed = JSON.parse(match[1]);
return parsed && typeof parsed === 'object' ? parsed : null;
} catch (_error) {
return null;
} catch (error) {
throw new SyntaxError(
`Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}`
);
}
}