forgedock 1.48.0 → 1.48.1

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.
@@ -266,9 +266,18 @@ export function tmpdirInputs(command) {
266
266
 
267
267
  const tmpInputProblem = (id, p) => `${id}: reads ${p}, which the command does not create; the run TMPDIR is per run, so the engine's rerun and red-green check will not find it. Create the file inside the command (\`… > ${p} && …\`) or use a path the repo tracks`;
268
268
 
269
- const E2E_WORD_RE = /\b(?:e2e|end-to-end|integration|playwright|cypress|live[- ]stack)\b/i;
270
- const WEAKEN_RE = /\b(?:no|not|without|drop(?:ped|s|ping)?|skip(?:ped|s|ping)?|replac\w+|downgrad\w+|instead of|rather than|in lieu of|swap\w*)\b/i;
271
- const MOCKED_RE = /\b(?:mock\w*|stub\w*|fake\w*|unit|vitest|jest|component tests?|route tests?)\b/i;
269
+ const E2E_TERM = String.raw`(?:e2e|end-to-end|integration|playwright|cypress|live[- ]stack)`;
270
+ const E2E_WORD_RE = new RegExp(String.raw`\b${E2E_TERM}\b`, "i");
271
+ const MOCKED_TERM = String.raw`(?:mock\w*|stub\w*|fake\w*|unit|vitest|jest|component tests?|route tests?)`;
272
+ const MOCKED_RE = new RegExp(String.raw`\b${MOCKED_TERM}`, "i");
273
+ // #5116: a negated mock ("do not mock", "no stubs") or a fake clock is not a substitute for the e2e check.
274
+ const NEG_MOCK_RE = /\b(?:do(?:es)? not|don't|never|no|not|without)\s+(?:use\s+|any\s+)?(?:mock\w*|stub\w*|fake\w*)/gi;
275
+ const FAKE_CLOCK_RE = /\bfake[- ](?:timers?|clocks?)\b/gi;
276
+ // A weakener governs the e2e term only within a few words and the same clause (no . or ; between them).
277
+ const GAP = String.raw`(?:[\s,]+\w+){0,3}?[\s,]+`;
278
+ const DROP_BEFORE_RE = new RegExp(String.raw`\b(?:no|not|without|never|drop(?:ped|s|ping)?|skip(?:ped|s|ping)?|replac\w+|downgrad\w+|swap\w*)${GAP}${E2E_TERM}\b`, "i");
279
+ const DROP_AFTER_RE = new RegExp(String.raw`\b${E2E_TERM}${GAP}(?:replaced|dropped|skipped|swapped|downgraded)\b`, "i");
280
+ const INSTEAD_OF_RE = new RegExp(String.raw`\b${MOCKED_TERM}[^.;]{0,60}?\b(?:instead of|rather than|in lieu of)\b${GAP}${E2E_TERM}\b`, "i");
272
281
  const PROBE_RE = /forge-probe|acceptance[- ]probe/i;
273
282
  const NEG_PROBE_RE = /\b(?:no|not|without|never)\b[^.;]{0,30}(?:forge-probe|acceptance[- ]probe)/i;
274
283
  /**
@@ -282,7 +291,9 @@ export function weakenedE2eDecisions(c) {
282
291
  const elsewhere = (id) => JSON.stringify({ r: c?.requirements ?? [], d: c?.deliverables ?? [], k: decisions.filter((k) => k?.id !== id) });
283
292
  return decisions.filter((k) => {
284
293
  const choice = String(k?.choice ?? "");
285
- if (!E2E_WORD_RE.test(choice) || !WEAKEN_RE.test(choice) || !MOCKED_RE.test(choice)) return false;
294
+ if (!E2E_WORD_RE.test(choice)) return false;
295
+ const mocked = choice.replace(NEG_MOCK_RE, " ").replace(FAKE_CLOCK_RE, " ");
296
+ if (!MOCKED_RE.test(mocked) || !(DROP_BEFORE_RE.test(mocked) || DROP_AFTER_RE.test(mocked) || INSTEAD_OF_RE.test(mocked))) return false;
286
297
  const own = `${choice} ${k?.justification ?? ""}`;
287
298
  return !((PROBE_RE.test(own) && !NEG_PROBE_RE.test(own)) || /forge-probe/i.test(elsewhere(k?.id)));
288
299
  }).map((k) => k.id).filter(Boolean);
@@ -1228,7 +1239,7 @@ export function bucketProblems(r) {
1228
1239
  const POST_PROMOTION_RE = /(?:after|post)[- ]?(?:promotion|release|deploy)|in production after|production[- ]verif|promot(?:e|ion)|rollout|purge|deploy[- ]time/i;
1229
1240
  export const READONLY_PROD_RE = /\bssh\s+[\w.-]*-monitor\b|\bobs-psql\b|\bprod(?:uction)?[ _-]monitor\b|\bread[- ]only\b[^.]{0,40}\bprod(?:uction)?\b|\bprod(?:uction)?\b[^.]{0,30}\b(?:query|queries|capture|telemetry|logs?)\b/i;
1230
1241
  /** An item that truly needs the outside world (or a write to production) stays external even when it also reads production. */
1231
- export const EXTERNAL_NEED_RE = /credential|secret|api[- ]?key|token|password|passphrase|ssh[- ]?keys?\b|private[- ]key|\blogin\b|\bsudo\b|spend|budget|billing|purchase|payment|account|third[- ]party|vendor|customer|contract sign|legal|\b(?:delete|drop|truncate|wipe|destroy|modify|migrate|write)\b/i;
1242
+ export const EXTERNAL_NEED_RE = /credential|secret|api[- ]?key|token|password|passphrase|ssh[- ]?keys?\b|private[- ]key|\blogin\b|\bsudo\b|spend|budget|billing|purchase|payment|account|third[- ]party|vendor|customer|contract sign|legal|\b(?:delete|drop|truncate|wipe|destroy|modify|migrate|write|update|restart|rotate|remove|grant|alter)\b/i;
1232
1243
  /**
1233
1244
  * #5078: where an owner deferral goes. `owner:orchestrator`, `owner:promote:*` or post-promotion text -> post-deploy (the
1234
1245
  * rollup); read-only production data or capture (with no credential/spend/account/third-party/customer need) -> host; else external (credentials, spend, an account, a third party or a customer).
@@ -1325,6 +1325,16 @@ describe("#5087 a plan decision may not weaken an e2e/integration criterion to m
1325
1325
  assert.deepEqual(weakenedE2eDecisions({ ...C, deliverables: [...C.deliverables, { id: "D9", path: "x.md", change: "declare a forge-probe block" }], decisions: [K2] }), []);
1326
1326
  assert.deepEqual(weakenedE2eDecisions({ ...C, decisions: [{ ...K2, justification: "no acceptance probe" }] }), ["K2"]);
1327
1327
  });
1328
+ it("#5116 negated or strengthening wording is not a weakening; real weakenings still are", () => {
1329
+ for (const choice of ["Run the Playwright e2e suite against the live stack; do not mock the API", "Integration tests use fake timers, no sleeps", "E2E check hits the real server rather than a stubbed one"]) {
1330
+ const c = { ...C, decisions: [{ id: "K1", choice, source: "doc" }] };
1331
+ assert.deepEqual(weakenedE2eDecisions(c), [], choice);
1332
+ assert.ok(!validateContract(c, ISSUE).problems.some((p) => p.startsWith("K1:")), choice);
1333
+ }
1334
+ for (const choice of ["No Playwright e2e; integration coverage via route and component tests", "Replace the e2e test with mocked unit tests", "Skip e2e, use vitest instead", "Use mocked unit tests instead of the e2e check"]) {
1335
+ assert.deepEqual(weakenedE2eDecisions({ ...C, decisions: [{ id: "K1", choice, source: "doc" }] }), ["K1"], choice);
1336
+ }
1337
+ });
1328
1338
  it("leaves unrelated decisions and contracts without decisions alone", () => {
1329
1339
  assert.deepEqual(weakenedE2eDecisions(C), []);
1330
1340
  assert.deepEqual(weakenedE2eDecisions({ ...C, decisions: [{ id: "K1", choice: "Run the Playwright e2e suite against the live stack", source: "doc" }, { id: "K3", choice: "Use unit tests with mocks for the parser", source: "spec" }] }), []);
@@ -1383,4 +1393,12 @@ describe("release review #5103", () => {
1383
1393
  }
1384
1394
  assert.equal(route5103({ bucket: "owner:decide", why: "Should we delete production data older than 90 days?" }), "external");
1385
1395
  });
1396
+ it("#5115: production-mutating verbs naming a prod monitor stay external, never host", () => {
1397
+ for (const why of ["ssh ovh-monitor and restart the worker", "rotate the prod monitor webhook", "update the production telemetry config",
1398
+ "remove the stale prod monitor alert", "grant read-only prod access to the analyst", "alter the production query timeout"]) {
1399
+ assert.equal(RO5103.test(why), true, `read-only prod phrase: ${why}`);
1400
+ assert.equal(host(why), false, why);
1401
+ assert.equal(route5103({ bucket: "owner:x", why }), "external", why);
1402
+ }
1403
+ });
1386
1404
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.48.0",
3
+ "version": "1.48.1",
4
4
  "description": "Autonomous development pipeline for coding agents. GitHub as institutional memory, with native Claude Code and OpenCode entrypoints.",
5
5
  "bin": {
6
6
  "forgedock": "bin/forgedock.mjs"
@@ -69,7 +69,7 @@ CLASSES="engineering post-deploy external ops verify-now host"
69
69
  # bin/lean/conformance.mjs ownerRoute; word boundaries spelled (^|[^[:alnum:]_]) / ([^[:alnum:]_]|$), no \b, for BSD grep (bash 3.2 hosts).
70
70
  POSTPROM_ERE='(after|post)[- ]?(promotion|release|deploy)|in production after|production[- ]verif|promot(e|ion)|rollout|purge|deploy[- ]time'
71
71
  ROPROD_ERE='ssh[[:space:]]+[[:alnum:]_.-]*-monitor([^[:alnum:]_]|$)|(^|[^[:alnum:]_])obs-psql([^[:alnum:]_]|$)|(^|[^[:alnum:]_])prod(uction)?[ _-]monitor([^[:alnum:]_]|$)|(^|[^[:alnum:]_])read[- ]only[^[:alnum:]_.]([^.]{0,38}[^[:alnum:]_.])?prod(uction)?([^[:alnum:]_]|$)|(^|[^[:alnum:]_])prod(uction)?[^[:alnum:]_.]([^.]{0,28}[^[:alnum:]_.])?(query|queries|capture|telemetry|logs?)([^[:alnum:]_]|$)'
72
- EXTNEED_ERE='credential|secret|api[- ]?key|token|password|passphrase|ssh[- ]?keys?([^[:alnum:]_]|$)|private[- ]key|(^|[^[:alnum:]_])login([^[:alnum:]_]|$)|(^|[^[:alnum:]_])sudo([^[:alnum:]_]|$)|spend|budget|billing|purchase|payment|account|third[- ]party|vendor|customer|contract sign|legal|(^|[^[:alnum:]_])(delete|drop|truncate|wipe|destroy|modify|migrate|write)([^[:alnum:]_]|$)'
72
+ EXTNEED_ERE='credential|secret|api[- ]?key|token|password|passphrase|ssh[- ]?keys?([^[:alnum:]_]|$)|private[- ]key|(^|[^[:alnum:]_])login([^[:alnum:]_]|$)|(^|[^[:alnum:]_])sudo([^[:alnum:]_]|$)|spend|budget|billing|purchase|payment|account|third[- ]party|vendor|customer|contract sign|legal|(^|[^[:alnum:]_])(delete|drop|truncate|wipe|destroy|modify|migrate|write|update|restart|rotate|remove|grant|alter)([^[:alnum:]_]|$)'
73
73
  owner_route() { # why -> post-deploy | host | external
74
74
  case "$1" in "owner: orchestrator"*|"owner: promote"*) echo "post-deploy"; return 0 ;; esac
75
75
  if printf '%s' "$1" | grep -qiE "$POSTPROM_ERE" && ! printf '%s' "$1" | grep -qiE "$EXTNEED_ERE"; then echo "post-deploy"
@@ -26,6 +26,12 @@ external|needs the prod ssh key to read production logs
26
26
  external|product manager decides the pricing page
27
27
  external|production dataset retention policy
28
28
  external|provide the Stripe API key, needed before rollout
29
+ external|ssh ovh-monitor and restart the worker
30
+ external|rotate the prod monitor webhook
31
+ external|update the production telemetry config
32
+ external|remove the stale prod monitor alert
33
+ external|grant read-only prod access to the analyst
34
+ external|alter the production query timeout
29
35
  post-deploy|verify the dashboard after release
30
36
  CASES
31
37
  echo "passed=$PASS failed=$FAIL"