diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1b4d45..9b09d989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Test-only prerequisite: standalone non-UTC freshness probe + +- Backport only the two relevant test-file changes from reviewed + `84d11af7658d91c3d3392831a088b89f54279b1d`. Replace redundant nested Jest + startup with a real Node leaf loading actual TypeScript source. Preserve + the 30-second child and 35-second parent deadlines and all 21 cases. +- Require runtime entrypoint/loader identity, New York January offset 300, + exactly one snapshot read and both UTC timestamps. The retained full-hook + 30,119ms ETIMEDOUT is the observed RED, not a timestamp assertion failure. + Reinstating the former launcher fails the receipt assertion even when the + child exits normally; removing actual production UTC interpretation fails + both timestamp values (05:00Z instead of 00:00Z). Production source is restored + byte-identically. Focused freshness: 21 -> 21 PASS; targeted ESLint and + fixture syntax PASS. Full protected-gate and exact-tip review evidence belong + to the prerequisite PR, not the old isolated PASS. +- No product/version/lockfile, application/UI, performance runner, quota or + admission change. The preserved Hono branch remains blocked until this + separate prerequisite is reviewed and normally merged. + ### 0.55.58 consolidation onto token-lifetime master - Address hosted review's shared-cache binding collision before release: diff --git a/Session_starter.md b/Session_starter.md index 642b8f0b..1e46626b 100644 --- a/Session_starter.md +++ b/Session_starter.md @@ -1,6 +1,32 @@ ## SCIMServer - Condensed Session Memory -**Last verified:** 2026-10-22 +**Last verified:** 2026-10-23 + +### Separate timezone-harness prerequisite (2026-10-23) + +Branch `test/timezone-leaf-prerequisite-20261023` starts from verified master +`e1070ba28825afee46c953e1e2d5d518863303f2`. Backport only the two test files +from `84d11af7`, plus direct runtime launcher receipts and narrow documentation. +All 21 focused freshness cases pass with the original 30-second child and +35-second parent limits. Negative controls reject the actual former nested +launcher and an actual UTC-conversion defect; production source is restored. +No product bump (still 0.55.58), Hono, lockfile or performance-tooling change. + +| Date | Summary | +|---|---| +| 2026-10-23 | Isolate the reviewed standalone timezone harness as a prerequisite; focused GREEN and two discriminating negative controls retained. | + +- [x] ✅ COMPLETED: Confirm actual full-hook spawn-timeout RED and focused + real-leaf outcome/launcher controls, without relaxing deadlines. +- [ ] Publish via ordinary hook, exact-tip CI and hosted review; do not merge. + +Preserve Hono branch `chore/regen-lockfile-hono-security-20261023` at +`e18d32c04855e8acf88c8a3e02c9ec7b447cfb21` and qualification branch at +`f6f03a523b6370eccc63f1499c3c111dd5cdf200`. Their evidence and release holds +remain unchanged. The 39875689201 p99 failure is not rerun or waived. +**Design disposition: accepted.** A small test-only real-source fixture needs +no application abstraction. **Self-improvement: applied.** Require the child +to report its actual entrypoint and loader, not just return a success exit. ### Current master/performance reconciliation (2026-10-21) diff --git a/api/src/modules/endpoint/services/endpoint-cache-freshness.spec.ts b/api/src/modules/endpoint/services/endpoint-cache-freshness.spec.ts index 5ae05430..28bf891a 100644 --- a/api/src/modules/endpoint/services/endpoint-cache-freshness.spec.ts +++ b/api/src/modules/endpoint/services/endpoint-cache-freshness.spec.ts @@ -190,9 +190,6 @@ describe('Persistent endpoint cache freshness', () => { }); it('preserves Prisma UTC timestamp semantics for timezone-free PostgreSQL JSON', async () => { - if (process.env.SCIM_ENDPOINT_TIMEZONE_PROBE === '1') { - expect(new Date(2026, 0, 1).getTimezoneOffset()).toBe(300); - } const result = await service.getEndpoint(id); expect(result.createdAt).toBe(endpoint().createdAt.toISOString()); expect(result.updatedAt).toBe(endpoint().updatedAt.toISOString()); @@ -201,11 +198,10 @@ describe('Persistent endpoint cache freshness', () => { it('checks PostgreSQL timestamp hydration in a real non-UTC Node process', () => { const startedAt = Date.now(); const result = spawnSync(process.execPath, [ - require.resolve('jest/bin/jest'), - '--runInBand', '--runTestsByPath', __filename, - '--testNamePattern', 'preserves Prisma UTC timestamp semantics', + '--require', require.resolve('ts-node/register/transpile-only'), + require.resolve('../../../../test/fixtures/endpoint-timezone-probe.cjs'), ], { - env: { ...process.env, TZ: 'America/New_York', SCIM_ENDPOINT_TIMEZONE_PROBE: '1' }, + env: { ...process.env, TZ: 'America/New_York', PERSISTENCE_BACKEND: 'prisma' }, encoding: 'utf8', timeout: 30_000, }); @@ -220,6 +216,16 @@ describe('Persistent endpoint cache freshness', () => { if (result.status !== 0) { throw new Error(`Non-UTC timestamp regression failed:\n${result.stdout}\n${result.stderr}`); } + expect(result.stdout).toContain('"runner":"standalone-production-path"'); + expect(JSON.parse(result.stdout)).toEqual({ + runner: 'standalone-production-path', + entrypoint: require.resolve('../../../../test/fixtures/endpoint-timezone-probe.cjs'), + execArgv: ['--require', require.resolve('ts-node/register/transpile-only')], + timezoneOffset: 300, + snapshotReads: 1, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + }); }, 35_000); it('preserves an explicit timestamp offset without appending another timezone', async () => { diff --git a/api/test/fixtures/endpoint-timezone-probe.cjs b/api/test/fixtures/endpoint-timezone-probe.cjs new file mode 100644 index 00000000..53ff3b79 --- /dev/null +++ b/api/test/fixtures/endpoint-timezone-probe.cjs @@ -0,0 +1,66 @@ +const assert = require('node:assert/strict'); +const { EndpointService } = require('../../src/modules/endpoint/services/endpoint.service.ts'); +const { + PrismaEndpointLifecycleRepository, +} = require('../../src/infrastructure/repositories/prisma/prisma-endpoint-lifecycle.repository.ts'); + +async function main() { + assert.equal(require.main, module); + assert.equal(process.env.PERSISTENCE_BACKEND, 'prisma'); + const timezoneOffset = new Date(2026, 0, 1).getTimezoneOffset(); + assert.equal(timezoneOffset, 300); + const id = '76e3c596-22aa-4527-bd6d-234367d798e6'; + let snapshotReads = 0; + const prisma = { + async $queryRaw(_sql, endpointId, knownFingerprint) { + assert.equal(endpointId, id); + assert.equal(knownFingerprint, null); + snapshotReads++; + return [ + { + fingerprint: '00000000-0000-4000-8000-000000000001', + endpoint: { + id, + name: 'timezone-probe', + displayName: 'Timezone probe', + description: null, + active: true, + profile: { + schemas: [], + resourceTypes: [], + settings: { logFileEnabled: false }, + }, + createdAt: '2026-01-01T00:00:00.000', + updatedAt: '2026-01-01T00:00:00.000', + }, + }, + ]; + }, + }; + const logger = { + clearEndpointLevel() {}, + disableEndpointFileLogging() {}, + }; + const service = new EndpointService( + prisma, + logger, + new PrismaEndpointLifecycleRepository(prisma), + ); + const endpoint = await service.getEndpoint(id); + process.stdout.write( + JSON.stringify({ + runner: 'standalone-production-path', + entrypoint: process.argv[1], + execArgv: process.execArgv, + timezoneOffset, + snapshotReads, + createdAt: endpoint.createdAt, + updatedAt: endpoint.updatedAt, + }), + ); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/docs/CONTEXT_INSTRUCTIONS.md b/docs/CONTEXT_INSTRUCTIONS.md index 9cbbdb1c..85f9713b 100644 --- a/docs/CONTEXT_INSTRUCTIONS.md +++ b/docs/CONTEXT_INSTRUCTIONS.md @@ -9,6 +9,15 @@ ## Active Delivery Process +**2026-10-23 test-harness prerequisite:** a separate master-based branch ports +only the real-leaf timezone tests from reviewed `84d11af7`, with runtime +entrypoint/loader receipt checks. Original 30-second child/35-second parent +limits remain. All 21 focused cases pass; former nested launcher and actual +UTC-conversion negative controls fail as expected, with production restored. +No product bump, Hono/lockfile change or performance-tooling import. +The Hono branch stays preserved at e18d32c0 until this prerequisite is reviewed +and normally merged. No merge, deployment or new performance admission. + **2026-10-22 review follow-up:** hosted Copilot review succeeds after billing selection and identifies a real shared-cache binding collision. The generic service now keys by full ResourceType binding, separately from builtin keys. diff --git a/docs/ENDPOINT_SNAPSHOT_REVISION.md b/docs/ENDPOINT_SNAPSHOT_REVISION.md index 3e8106fc..b6e629bf 100644 --- a/docs/ENDPOINT_SNAPSHOT_REVISION.md +++ b/docs/ENDPOINT_SNAPSHOT_REVISION.md @@ -5,6 +5,67 @@ **Status:** Implemented and tested locally, not released or deployed. Performance admission and the independent release gates remain open. +## Test-harness prerequisite: real non-UTC leaf (2026-10-23) + +This section changes only test execution, not endpoint behavior or any frozen +performance result below. The master-based Hono remediation encountered the +same nested-Jest startup problem corrected on the preserved qualification +branch. Isolate the two test-file changes from +`84d11af7658d91c3d3392831a088b89f54279b1d` as a separate prerequisite. +No checkpoint053 Linux/finalization code, dependency pin, version or lockfile +belongs in this rollback unit. + +### Failure and root cause + +The actual normal Fast hook retained at +`test-results/hono-security-20261023/bootstrap-push-final.log` reports 6,209 +unit PASS and one FAIL. `endpoint-cache-freshness.spec.ts`'s +`checks PostgreSQL timestamp hydration in a real non-UTC Node process` throws +at line 213 after `spawnSync` at line 203 reaches its 30,000ms deadline: +30,119ms, null exit status, SIGTERM, ETIMEDOUT. That is a child-startup/execution +deadline failure, not a failed timestamp-value assertion. A prior isolated +pass does not clear that protected-gate failure. + +The former parent launches another complete Jest runner to select one test. +The correction launches Node with the existing +`ts-node/register/transpile-only` loader and a small leaf fixture calling the +actual `EndpointService.getEndpoint` source and lifecycle repository. Only +external SQL snapshot I/O and logger methods are stubbed; no conversion logic +is copied and no database is opened. The receipt proves the actual Node +entrypoint/loader, real January New York offset (300 minutes), exactly one +snapshot read and both timestamps at `2026-01-01T00:00:00.000Z`. +The child deadline stays 30 seconds and the parent test stays 35 seconds. + +### Focused proof and escape analysis + +| Check | Actual result | Claim and limit | +|---|---|---| +| Retained ordinary full-hook failure | ETIMEDOUT at 30,119ms | Observed RED; not fabricated timestamp failure | +| Former nested launcher reinstated, real child | FAIL on missing leaf receipt, child exited normally | Rejects the old design without manufactured load or a deadline change | +| Actual production UTC interpretation temporarily removed | FAIL, both fields 05:00Z instead of 00:00Z | Real leaf retains timestamp correctness assurance; production restored exactly | +| Complete focused freshness file after restoration | 21/21 PASS, 4.423s | Same case count and original deadlines | +| Fixture syntax and targeted ESLint | PASS | Existing tooling, no dependencies added | + +The two negative-control edits are not committed. Runtime entrypoint and +loader assertions remain permanent in the parent; the leaf additionally +asserts that it is the direct executable module. +Focused logs are retained in `test-results/timezone-prerequisite-20261023/`. +The exact-tip ordinary hook, CI and hosted review are reported in the +prerequisite PR; focused results alone do not qualify protected publication. + +**RCA classification:** Medium, test harness. The first unit gate detects the +deadline failure before publication. The earlier direct-child PASS could not +detect redundant parent/child Jest startup, so the escape was from overly +narrow execution evidence, not a missing timestamp assertion. +**Self-improvement: applied.** Test both the actual real-source outcome and +the execution topology by requiring runtime leaf identity. +**Design disposition: accepted.** Keep the small test fixture cohesive; no +production seam or new generic process framework is justified. +**Provenance:** reconciled against the retained full-hook log and this fresh +continuation's actual focused GREEN/negative-control logs. The old isolated +PASS is retained as an observation, not substituted for the hook failure. +No deleted transcript was restored or reconstructed. + ## 1. Executive summary **Current result:** the latest immutable local candidate passes eleven of diff --git a/docs/strategy/ENGINEERING_LESSONS_AND_PATTERNS.md b/docs/strategy/ENGINEERING_LESSONS_AND_PATTERNS.md index 2559da14..578c0c18 100644 --- a/docs/strategy/ENGINEERING_LESSONS_AND_PATTERNS.md +++ b/docs/strategy/ENGINEERING_LESSONS_AND_PATTERNS.md @@ -16,6 +16,18 @@ ## 1. The self-improvement loop (how this doc is fed and used) +### A real-timezone regression should launch one real leaf, not another suite + +A real child process is necessary to test Node's non-UTC date interpretation; +a second full Jest startup is not. The unchanged 30-second nested-run deadline +failed again when a separate branch started from master without the reviewed +fixture correction. Preserve the real subprocess and both existing deadlines, +but load the actual TypeScript path through a small leaf using existing tooling. +Require a runtime receipt for entrypoint, loader, timezone offset, read count +and timestamps. A successful exit or isolated PASS cannot prove that topology. +Reinstate the old launcher as a negative control and ensure it fails even when +its child succeeds. Keep production code byte-identical after mutation checks. + ### Request variants and rolling windows need semantic assertions A successful response cannot prove which request headers were sent. When a