@carecard/auth-util 3.12.0 → 3.14.0

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.
@@ -7,6 +7,14 @@ Non-negotiable root-cause solution rule: Always identify and solve the verified
7
7
 
8
8
  # CareCard Workspace Standards
9
9
 
10
+ Mandatory companion: load
11
+ `$pkg-auth-util-coding-standards-and-best-practices` before this skill for every
12
+ task in this repository.
13
+
14
+ Non-negotiable test order invariance rule: Every test must pass independently of which tests run before or after it, and the suite must pass in every execution order. Each test must establish the state it needs, isolate mutable state, and clean up state it owns; it must never rely on another test's setup, mutations, or cleanup. Default test, CI, and Husky commands must use the test framework's ordinary ordering and must not force randomized ordering. Random-order execution is an explicit diagnostic only, and every failure it exposes must be fixed at the root cause.
15
+
16
+ Non-negotiable parallel test execution rule: Run independent test files in parallel with repository-native worker support wherever resource isolation makes parallel execution safe. Tests that share a mutable database, application server, browser state, filesystem fixture, port, or cluster resource must remain in an explicitly isolated serial group until every worker owns a separate resource. Parallel execution must preserve ordinary test selection and must never use randomized ordering, retries, locks, or error suppression to conceal coupling.
17
+
10
18
  Non-negotiable TDD rule: Always write the failing test first, run it to confirm it fails for the intended reason, then implement the code and rerun the test until it passes. Test Driven Development is required for all coding work and must not be skipped. For documentation- or skill-only edits, add or update the relevant validation check before changing the prose.
11
19
 
12
20
  This requirement is non-negotiable and may be overridden only with the user's
@@ -26,6 +34,8 @@ Non-negotiable repository isolation rule: Every repository must run its Husky ho
26
34
 
27
35
  Non-negotiable error and warning rule: Never suppress, silence, hide, downgrade, filter, ignore, skip, or bypass errors or warnings from code, tests, tools, compilers, linters, or validation. Fix the root cause, then rerun the affected check and require a clean result. Expected error-path tests may assert errors, but must not conceal unexpected failures.
28
36
 
37
+ Non-negotiable TypeScript type rule: Never use the TypeScript type `any`; always use specific domain types, generics, existing project types, or `unknown` with explicit narrowing in all TypeScript-family files (`.ts`, `.tsx`, `.mts`, `.cts`, and `.d.ts`).
38
+
29
39
  Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
30
40
 
31
41
  ## Purpose
@@ -400,7 +410,7 @@ the authenticated dashboard.
400
410
  - `ms-auth` follows the shared PostgreSQL/RLS pattern: auth tables live in the `carecard` schema, RLS is enabled and forced on every auth table, and application runtime queries use the unprivileged database role.
401
411
  - Auth table policies allow normal JWT users to access only self-owned rows. Do not add redundant `user_id = <jwt sub>` SQL predicates to duplicate self-row checks when RLS owns the authorization decision.
402
412
  - A JWT payload containing `roles: ["ad"]` is the auth-service super-admin signal and can perform any action on auth tables. Dashboard code may map that role to `super_admin`, but backend auth RLS must not require a separate database role row for that bypass.
403
- - Public auth flows such as registration, login, confirmation, recovery, visitor creation, and service user lookup must use narrow system contexts (`system_create`, `system_login`, `system_confirm`, `system_recovery`, `system_visitor`, `system_service`) instead of privileged runtime queries.
413
+ - Public auth flows such as registration, login, recovery, visitor creation, and service user lookup must use narrow system contexts (`system_create`, `system_login`, `system_recovery`, `system_visitor`, `system_service`) instead of privileged runtime queries.
404
414
 
405
415
  - `ms-auth` controller exports use concise action names such as `loginUser`,
406
416
  `registerUser`, `getUserDetail`, and `renewJwt`; route middleware and router
@@ -410,7 +420,7 @@ the authenticated dashboard.
410
420
 
411
421
  ## Security Requirements
412
422
 
413
- - Treat authentication, authorization, JWT, password, email confirmation,
423
+ - Treat authentication, authorization, JWT, password, email verification,
414
424
  recovery, file upload, CORS, rate limits, and error response behavior as
415
425
  security-sensitive.
416
426
  - Never log or return secrets, tokens, passwords, credentials, private keys,
@@ -13,6 +13,8 @@ Non-negotiable repository isolation rule: Every repository must run its Husky ho
13
13
 
14
14
  Non-negotiable error and warning rule: Never suppress, silence, hide, downgrade, filter, ignore, skip, or bypass errors or warnings from code, tests, tools, compilers, linters, or validation. Fix the root cause, then rerun the affected check and require a clean result. Expected error-path tests may assert errors, but must not conceal unexpected failures.
15
15
 
16
+ Non-negotiable TypeScript type rule: Never use the TypeScript type `any`; always use specific domain types, generics, existing project types, or `unknown` with explicit narrowing in all TypeScript-family files (`.ts`, `.tsx`, `.mts`, `.cts`, and `.d.ts`).
17
+
16
18
  Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
17
19
 
18
20
  ## Purpose
@@ -247,3 +249,21 @@ repository's agents-only Git workflow:
247
249
  Do not commit or push `.agents` guidance changes directly from `development`
248
250
  or `main`. Do not stage unrelated files, generated output, dependency folders,
249
251
  build artifacts, logs, or `.DS_Store`.
252
+
253
+ ## Fail-Closed Test Lifecycle Audit
254
+
255
+ The current package tests own no HTTP listener, database pool, Kafka client,
256
+ background timer, or child process after completion. Mocha's test timeout fails
257
+ a stalled async test, the suites run without bail or forced exit, and npm
258
+ preserves each command's nonzero status. Keep natural process exit as the open
259
+ handle regression check; validation must not hide failures with retries, forced
260
+ success, skipped tests, or output suppression.
261
+
262
+ Do not add unpublished executable validation code to a `pkg-*` repository. If a
263
+ future test owns a long-lived resource or demonstrates a post-suite hang, add a
264
+ contract-tested process watchdog through the coordinated package version,
265
+ publish, and consumer propagation workflow. That watchdog must return
266
+ immediately when no helper remains, allow only a bounded 250 ms settlement
267
+ window for already-stopping helpers, fail persistent descendants, preserve
268
+ failures and output, use exit code `124` only for a real outer deadline, and
269
+ remain a final guard rather than a substitute for explicit cleanup.
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: pkg-auth-util-coding-standards-and-best-practices
3
+ description: 'Mandatory for every pkg-auth-util task, including analysis, clarification, planning, implementation, review, debugging, documentation, public API work, skill maintenance, and validation. Use before every narrower skill.'
4
+ ---
5
+
6
+ # Pkg Auth Util Coding Standards And Best Practices
7
+
8
+ Non-negotiable root-cause solution rule: Always identify and solve the verified
9
+ root cause, use the stronger solution, and deliver a correct, durable,
10
+ production-quality result. Never treat a temporary workaround, resource
11
+ increase, retry, suppression, bypass, or symptom-only patch as completion.
12
+ Validate the root-cause fix against the real failing workflow and prove the end
13
+ state.
14
+
15
+ Non-negotiable error and warning rule: Never suppress, silence, hide, downgrade,
16
+ filter, ignore, skip, or bypass errors or warnings from code, tests, tools,
17
+ compilers, linters, or validation. Fix the root cause, then rerun the affected
18
+ check and require a clean result. Expected error-path tests may assert errors,
19
+ but must not conceal unexpected failures.
20
+
21
+ Non-negotiable repository isolation rule: Every repository must run its Husky
22
+ hooks and tests using only files, code, fixtures, dependencies, and services
23
+ contained within that repository. Tests and Husky scripts must not import,
24
+ require, read, execute, or otherwise depend on sibling repositories or paths
25
+ outside the repository root. app-e2e-tests is the only exception because
26
+ cross-repository end-to-end testing is its explicit responsibility.
27
+
28
+ ## Mandatory Use And Authorities
29
+
30
+ Load this skill before doing any work in `pkg-auth-util`, including read-only
31
+ and documentation-only work. Then load every narrower skill that owns the
32
+ affected package contract.
33
+
34
+ Use these existing authorities instead of duplicating them:
35
+
36
+ - `$carecard-workspace-standards` for TDD, root-cause solutions, dependencies,
37
+ errors, isolation, and repository workflow;
38
+ - `$software-design-patterns-and-clean-code` for design, DRY, KISS,
39
+ testability, and clean-code details;
40
+ - `$pkg-auth-util-auth-crypto-library` for cryptography, package architecture,
41
+ exports, types, security, and validation; and
42
+ - `$pkg-publish` only when runtime package artifacts or consumer versions must
43
+ be published and propagated.
44
+
45
+ This skill adds the function-evolution, direct-contract, composition, and
46
+ completion rules below without weakening those companion skills.
47
+
48
+ ## Requirement Judgment
49
+
50
+ 1. Read the complete request and inspect implementation, public exports,
51
+ declarations, tests, documentation, and consumers before deciding how to
52
+ change the package.
53
+ 2. Translate the request into a coherent technical contract. Do not apply
54
+ wording mechanically when it is contradictory, unsafe, impossible, or
55
+ incompatible with cryptographic or package architecture.
56
+ 3. Make low-risk, reversible assumptions only when they preserve requested
57
+ behavior and scope.
58
+ 4. Ask for clarification when an unresolved choice would materially change a
59
+ public API, cryptographic behavior, security, consumer behavior, destructive
60
+ scope, or the repositories that must change.
61
+ 5. Explain architectural tradeoffs before a major API, type, crypto, module,
62
+ package, or dependency change.
63
+
64
+ ## Scope And Quality
65
+
66
+ - Treat every workspace repository as independent and validate it from its own
67
+ root.
68
+ - Update every skill, document, source, runtime test, type test, export,
69
+ declaration, consumer, and package version genuinely required for a coherent
70
+ task.
71
+ - Do not broaden the task into unrelated cleanup.
72
+ - Preserve CommonJS structure, established public surfaces, naming, and test
73
+ conventions unless the task explicitly replaces them.
74
+ - Prefer Node core and existing helpers over new dependencies.
75
+ - Prefer readable direct implementation over clever compression or a temporary
76
+ workaround.
77
+ - Use meaningful names that describe authentication and cryptographic intent.
78
+
79
+ ## Function Evolution
80
+
81
+ Before changing a function's behavior or signature, inventory every direct,
82
+ indirect, test, exported, callback, configuration-driven, and dynamic consumer.
83
+
84
+ - If exactly one consumer is proven, change the function only when required.
85
+ - If two or more consumers exist, do not change the shared function's behavior.
86
+ Create a new purpose-named function and migrate only intended consumers.
87
+ - If every consumer needs the new contract, migrate all consumers and delete
88
+ the old function after proving it unused.
89
+ - Treat every exported, declared, public, callback, or dynamically discovered
90
+ function as shared unless single use is conclusively proven.
91
+ - Do not add caller branches, mode flags, or optional parameters merely to
92
+ make one shared function serve incompatible contracts.
93
+ - Cover the new function, public surface, types, and every migrated consumer
94
+ through TDD.
95
+
96
+ ## Direct Contract Without Backward Compatibility
97
+
98
+ When the active task replaces a contract, implement the requested end state
99
+ directly. Do not add legacy aliases, deprecated wrappers, compatibility
100
+ overloads, duplicate exports, dual paths, transitional names, or fallback
101
+ behavior solely to preserve the superseded contract.
102
+
103
+ Delete obsolete functions and exports after all intended consumers have
104
+ migrated and repository-native search, runtime tests, and type tests prove them
105
+ unused. This does not authorize unrelated API removal. If an existing
106
+ published or security contract requires compatibility and the request does not
107
+ clearly supersede it, explain the conflict and ask before changing it.
108
+
109
+ ## TDD And Root-Cause Gate
110
+
111
+ Follow `$carecard-workspace-standards` and
112
+ `$pkg-auth-util-auth-crypto-library` for the complete failing-test-first and
113
+ root-cause workflow. Documentation and skill changes require a focused
114
+ structural validation before prose changes. Do not accept retries, suppressed
115
+ diagnostics, weakened types, disabled tests, forced success, compatibility
116
+ patches, or symptom-only workarounds as completion.
117
+
118
+ ## Function Size
119
+
120
+ Every new or materially changed function must contain at most 25 logical code
121
+ lines.
122
+
123
+ - Count executable statements, branches, loop headers, side-effecting calls,
124
+ returns, and throws.
125
+ - Exclude signatures, type-only declarations, blank lines, comments, and
126
+ isolated braces.
127
+ - Extract cohesive purpose-named helpers and compose them when needed.
128
+ - Keep crypto parsing, validation, transformation, signing, and error
129
+ responsibilities explicit rather than hiding them in one long function.
130
+ - Avoid meaningless forwarding wrappers and do not refactor untouched
131
+ functions solely to satisfy this limit.
132
+
133
+ ## UI Composition
134
+
135
+ This package does not own UI. If a package contract requires UI changes, make
136
+ them in the owning app repository. There, create focused components, compose
137
+ existing and new components, and delete obsolete components only after proving
138
+ them unused and replaced.
139
+
140
+ ## Skills, Documentation, And Database Boundaries
141
+
142
+ - Update affected skills, README guidance, examples, exports, and declarations
143
+ with behavior or validation changes.
144
+ - Reference existing authoritative skills instead of copying their details.
145
+ - If work reaches an `ms-*` database, change SQL only in the owning repository
146
+ using its database-migration-ownership skill. For first-create work, edit the
147
+ existing migration and matching rollback directly rather than adding a
148
+ compatibility migration.
149
+ - Keep persistence and service behavior in their owning repositories.
150
+
151
+ ## Completion
152
+
153
+ 1. Review each changed repository's diff and status independently.
154
+ 2. Run focused runtime and type tests, then all broader checks required by
155
+ local skills.
156
+ 3. If every changed file in a repository is Markdown (`*.md`), skip Husky and
157
+ run only focused Markdown validation.
158
+ 4. If any changed file is not Markdown, run every direct `.husky` script. If
159
+ none exists, run the strongest repository-native focused validation.
160
+ 5. Fix every in-scope failure at its root cause and rerun the exact command.
161
+ 6. Report exact commands, results, limitations, and remaining risk.
162
+ 7. Do not perform remote Git or GitHub operations unless explicitly requested.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: 'Pkg Auth Util Coding Standards'
3
+ short_description: 'Apply mandatory auth package standards'
4
+ default_prompt: 'Use $pkg-auth-util-coding-standards-and-best-practices before every pkg-auth-util task and apply all narrower skills that the task requires.'
@@ -13,6 +13,8 @@ Non-negotiable repository isolation rule: Every repository must run its Husky ho
13
13
 
14
14
  Non-negotiable error and warning rule: Never suppress, silence, hide, downgrade, filter, ignore, skip, or bypass errors or warnings from code, tests, tools, compilers, linters, or validation. Fix the root cause, then rerun the affected check and require a clean result. Expected error-path tests may assert errors, but must not conceal unexpected failures.
15
15
 
16
+ Non-negotiable TypeScript type rule: Never use the TypeScript type `any`; always use specific domain types, generics, existing project types, or `unknown` with explicit narrowing in all TypeScript-family files (`.ts`, `.tsx`, `.mts`, `.cts`, and `.d.ts`).
17
+
16
18
  Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
17
19
 
18
20
  ## Purpose
package/AGENTS.md ADDED
@@ -0,0 +1,15 @@
1
+ # Codex Instructions
2
+
3
+ ## Non-negotiable Codex banked-reset requirement
4
+
5
+ - Never use or consume a banked Codex rate-limit reset automatically.
6
+ - Before using any banked Codex rate-limit reset, stop, ask the user for explicit, direct approval for that specific reset, and wait for their reply.
7
+ - Never treat earlier approval, a standing instruction, silence, urgency, an unfinished task, or a request to continue as approval for a future reset.
8
+ - Do not invoke `/usage` redemption, a reset-consumption action, a reset API or tool, or any equivalent mechanism unless the user explicitly approved that specific reset.
9
+ - If a Codex limit is reached without that approval, pause and let the user reset it manually. Never consume a banked reset to keep working.
10
+
11
+ ## Repository validation contracts
12
+
13
+ Non-negotiable repository isolation rule: Every repository must run its Husky hooks and tests using only files, code, fixtures, dependencies, and services contained within that repository. Tests and Husky scripts must not import, require, read, execute, or otherwise depend on sibling repositories or paths outside the repository root. app-e2e-tests is the only exception because cross-repository end-to-end testing is its explicit responsibility.
14
+
15
+ Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Utility functions for authentication and authorization in the CareCard ecosystem.
3
3
  */
4
4
 
5
- import { Request } from 'express';
5
+ import type { Request } from 'express';
6
6
 
7
7
  /**
8
8
  * Represents the standard JWT header structure.
@@ -14,7 +14,7 @@ export interface JwtHeader {
14
14
  typ?: string;
15
15
 
16
16
  /** Any other custom header fields. */
17
- [key: string]: any;
17
+ [key: string]: unknown;
18
18
  }
19
19
 
20
20
  /**
@@ -35,7 +35,7 @@ export interface JwtPayload {
35
35
  roles?: string[];
36
36
 
37
37
  /** Any other custom payload fields. */
38
- [key: string]: any;
38
+ [key: string]: unknown;
39
39
  }
40
40
 
41
41
  /**
@@ -89,10 +89,10 @@ export interface JwtRequestObject {
89
89
  header: JwtHeader;
90
90
  payload: JwtPayload;
91
91
  age?: number;
92
- jwtClientId: (req?: any) => string | undefined;
92
+ jwtClientId: (req?: AuthenticatedRequest) => string | undefined;
93
93
  doesJwtUserHasRole: (role: string) => boolean;
94
94
  isJwtExpired: (jwtValiditySeconds?: number) => boolean;
95
- jwtAgeInSeconds: (req?: any) => number;
95
+ jwtAgeInSeconds: (req?: AuthenticatedRequest) => number;
96
96
  }
97
97
 
98
98
  /**
@@ -101,7 +101,7 @@ export interface JwtRequestObject {
101
101
  export interface VisitorRequestObject {
102
102
  header: JwtHeader;
103
103
  payload: JwtPayload;
104
- visitorClientId: (req?: any) => string | undefined;
104
+ visitorClientId: (req?: AuthenticatedRequest) => string | undefined;
105
105
  }
106
106
 
107
107
  /**
@@ -248,14 +248,14 @@ export const stringUtilAuth: {
248
248
  * @param object - The object to serialize.
249
249
  * @returns URL-safe base64 string.
250
250
  */
251
- objectToBase64UrlSafeString: (object: any) => string;
251
+ objectToBase64UrlSafeString: (object: unknown) => string;
252
252
  /**
253
253
  * Parses a URL-safe base64 string into an object.
254
254
  * @deprecated Use native Buffer methods or other modern alternatives.
255
255
  * @param urlSafeBase64String - URL-safe base64 string.
256
256
  * @returns The parsed object.
257
257
  */
258
- urlSafeBase64ToObject: (urlSafeBase64String: string) => any;
258
+ urlSafeBase64ToObject: (urlSafeBase64String: string) => unknown;
259
259
  };
260
260
 
261
261
  /**
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@carecard/auth-util",
3
- "version": "3.12.0",
3
+ "version": "3.14.0",
4
4
  "repository": "https://github.com/CareCard-ca/pkg-auth-util.git",
5
5
  "description": "Auth utility functions",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "scripts": {
9
- "test": "mocha --recursive",
10
- "test:types": "tsc --noEmit && mocha -r ts-node/register test/types.test.ts",
11
- "test:coverage": "tsc --noEmit && nyc mocha --recursive -r ts-node/register 'test/**/*.{js,ts}'",
9
+ "test": "npm run test:order && node test/index.test.js",
10
+ "test:order": "node --test scripts/testOrder/randomizeTestOrder.test.mjs scripts/testOrder/testOrderPolicy.test.mjs scripts/testParallel/runIndexedMochaTests.test.mjs scripts/testParallel/parallelTestPolicy.test.mjs",
11
+ "test:types": "npm run test:order && tsc --noEmit && mocha --require ./scripts/testOrder/randomizeTestOrder.cjs -r ts-node/register test/types.test.ts",
12
+ "test:coverage": "npm run test:order && tsc --noEmit && nyc node test/index.test.js",
12
13
  "test:All": "npm run test && npm run test:types",
13
14
  "format": "prettier --write .",
14
15
  "format:check": "prettier --check .",
@@ -41,14 +42,15 @@
41
42
  "typescript": "6.0.3"
42
43
  },
43
44
  "dependencies": {
44
- "@carecard/common-util": "3.12.0",
45
- "@carecard/validate": "3.12.0",
45
+ "@carecard/common-util": "3.14.0",
46
+ "@carecard/validate": "3.14.0",
46
47
  "@types/express": "5.0.6"
47
48
  },
48
49
  "overrides": {
49
50
  "diff": "8.0.4",
50
51
  "glob": "13.0.6",
52
+ "minimatch": "10.2.5",
51
53
  "serialize-javascript": "7.0.5",
52
- "js-yaml": "4.2.0"
54
+ "js-yaml": "4.3.0"
53
55
  }
54
56
  }
package/readme.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @carecard/auth-util
2
2
 
3
+ Non-negotiable test order invariance rule: Every test must pass independently of which tests run before or after it, and the suite must pass in every execution order. Each test must establish the state it needs, isolate mutable state, and clean up state it owns; it must never rely on another test's setup, mutations, or cleanup. Default test, CI, and Husky commands must use the test framework's ordinary ordering and must not force randomized ordering. Random-order execution is an explicit diagnostic only, and every failure it exposes must be fixed at the root cause.
4
+
3
5
  Non-negotiable root-cause solution rule: Always identify and solve the verified root cause, use the stronger solution, and deliver a correct, durable, production-quality result. Never treat a temporary workaround, resource increase, retry, suppression, bypass, or symptom-only patch as completion. Validate the root-cause fix against the real failing workflow and prove the end state.
4
6
 
5
7
  ![Tests Passing](https://github.com/CareCard-ca/pkg-auth-util/actions/workflows/ci.yml/badge.svg)
@@ -15,6 +17,8 @@ Non-negotiable repository isolation rule: Every repository must run its Husky ho
15
17
 
16
18
  Non-negotiable error and warning rule: Never suppress, silence, hide, downgrade, filter, ignore, skip, or bypass errors or warnings from code, tests, tools, compilers, linters, or validation. Fix the root cause, then rerun the affected check and require a clean result. Expected error-path tests may assert errors, but must not conceal unexpected failures.
17
19
 
20
+ Non-negotiable TypeScript type rule: Never use the TypeScript type `any`; always use specific domain types, generics, existing project types, or `unknown` with explicit narrowing in all TypeScript-family files (`.ts`, `.tsx`, `.mts`, `.cts`, and `.d.ts`).
21
+
18
22
  Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
19
23
 
20
24
  ## Features
@@ -150,3 +154,21 @@ The package is organized into several modules:
150
154
  - `stringUtilAuth`: String manipulation and format conversions.
151
155
 
152
156
  All modules are exported through the main `index.js`.
157
+
158
+ ## Fail-Closed Test Lifecycle Audit
159
+
160
+ The current package tests own no HTTP listener, database pool, Kafka client,
161
+ background timer, or child process after completion. Mocha's test timeout fails
162
+ a stalled async test, the suites run without bail or forced exit, and npm
163
+ preserves each command's nonzero status. Keep natural process exit as the open
164
+ handle regression check; validation must not hide failures with retries, forced
165
+ success, skipped tests, or output suppression.
166
+
167
+ Do not add unpublished executable validation code to a `pkg-*` repository. If a
168
+ future test owns a long-lived resource or demonstrates a post-suite hang, add a
169
+ contract-tested process watchdog through the coordinated package version,
170
+ publish, and consumer propagation workflow. That watchdog must return
171
+ immediately when no helper remains, allow only a bounded 250 ms settlement
172
+ window for already-stopping helpers, fail persistent descendants, preserve
173
+ failures and output, use exit code `124` only for a real outer deadline, and
174
+ remain a final guard rather than a substitute for explicit cleanup.
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const MAX_TEST_ORDER_SEED = 2_147_483_647;
4
+
5
+ function resolveTestOrderSeed(configuredSeed) {
6
+ if (configuredSeed === undefined) return undefined;
7
+ if (!/^[1-9]\d*$/.test(configuredSeed)) throw new Error('TEST_ORDER_SEED must be a positive 32-bit integer.');
8
+ const seed = Number(configuredSeed);
9
+ if (!Number.isSafeInteger(seed) || seed > MAX_TEST_ORDER_SEED) throw new Error('TEST_ORDER_SEED must be a positive 32-bit integer.');
10
+ return seed;
11
+ }
12
+ function createSeededRandom(seed) {
13
+ let state = seed;
14
+ return function nextRandomValue() {
15
+ state = (state + 0x6d2b79f5) | 0;
16
+ let value = Math.imul(state ^ (state >>> 15), 1 | state);
17
+ value = (value + Math.imul(value ^ (value >>> 7), 61 | value)) ^ value;
18
+ return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296;
19
+ };
20
+ }
21
+ function shuffleValues(values, random) {
22
+ for (let index = values.length - 1; index > 0; index -= 1) {
23
+ const replacementIndex = Math.floor(random() * (index + 1));
24
+ [values[index], values[replacementIndex]] = [values[replacementIndex], values[index]];
25
+ }
26
+ }
27
+ function shuffleSuiteTree(suite, random) {
28
+ for (const childSuite of suite.suites) shuffleSuiteTree(childSuite, random);
29
+ shuffleValues(suite.tests, random);
30
+ shuffleValues(suite.suites, random);
31
+ }
32
+ const mochaHooks = {
33
+ beforeAll() {
34
+ const seed = resolveTestOrderSeed(process.env.TEST_ORDER_SEED);
35
+ if (seed === undefined) return;
36
+ console.log(`Test order seed: ${seed} (reproduce with TEST_ORDER_SEED=${seed})`);
37
+ shuffleSuiteTree(this.test.parent, createSeededRandom(seed));
38
+ },
39
+ };
40
+ module.exports = { createSeededRandom, mochaHooks, resolveTestOrderSeed, shuffleSuiteTree };
@@ -0,0 +1,36 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+
4
+ import testOrderRandomizer from './randomizeTestOrder.cjs';
5
+
6
+ const { createSeededRandom, resolveTestOrderSeed, shuffleSuiteTree } = testOrderRandomizer;
7
+
8
+ function createSuiteTree() {
9
+ return {
10
+ suites: [
11
+ { title: 'alpha', suites: [], tests: [{ title: 'one' }, { title: 'two' }] },
12
+ { title: 'beta', suites: [], tests: [{ title: 'three' }, { title: 'four' }] },
13
+ { title: 'gamma', suites: [], tests: [{ title: 'five' }, { title: 'six' }] },
14
+ ],
15
+ tests: [{ title: 'root one' }, { title: 'root two' }, { title: 'root three' }],
16
+ };
17
+ }
18
+
19
+ test('uses ordinary ordering unless TEST_ORDER_SEED is explicitly supplied', () => {
20
+ assert.strictEqual(resolveTestOrderSeed(undefined), undefined);
21
+ assert.strictEqual(resolveTestOrderSeed('314159'), 314159);
22
+ for (const invalidSeed of ['', '0', '-1', '1.5', 'seed', '2147483648']) {
23
+ assert.throws(() => resolveTestOrderSeed(invalidSeed), /TEST_ORDER_SEED/);
24
+ }
25
+ });
26
+
27
+ test('shuffles nested suites and tests reproducibly', () => {
28
+ const firstTree = createSuiteTree();
29
+ const secondTree = createSuiteTree();
30
+
31
+ shuffleSuiteTree(firstTree, createSeededRandom(314159));
32
+ shuffleSuiteTree(secondTree, createSeededRandom(314159));
33
+
34
+ assert.deepStrictEqual(firstTree, secondTree);
35
+ assert.notDeepStrictEqual(firstTree, createSuiteTree());
36
+ });
@@ -0,0 +1,48 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import { test } from 'node:test';
5
+
6
+ const TEST_ORDER_INVARIANCE_RULE =
7
+ "Non-negotiable test order invariance rule: Every test must pass independently of which tests run before or after it, and the suite must pass in every execution order. Each test must establish the state it needs, isolate mutable state, and clean up state it owns; it must never rely on another test's setup, mutations, or cleanup. Default test, CI, and Husky commands must use the test framework's ordinary ordering and must not force randomized ordering. Random-order execution is an explicit diagnostic only, and every failure it exposes must be fixed at the root cause.";
8
+
9
+ function listRepositoryFiles() {
10
+ return execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], { encoding: 'utf8' })
11
+ .trim()
12
+ .split('\n')
13
+ .filter(Boolean);
14
+ }
15
+
16
+ function isRequiredTestGuidance(filePath) {
17
+ return (
18
+ /^readme\.md$/i.test(filePath) ||
19
+ filePath === '.codex/AGENTS.md' ||
20
+ filePath === '.junie/guidelines.md' ||
21
+ filePath === '.agents/skills/carecard-workspace-standards/SKILL.md' ||
22
+ /^\.agents\/skills\/[^/]*(?:test|testing)[^/]*\/(?:SKILL\.md|references\/[^/]*(?:test|testing|coding-principles)[^/]*\.md)$/i.test(
23
+ filePath,
24
+ )
25
+ );
26
+ }
27
+
28
+ test('keeps the non-negotiable test order rule in repository guidance', () => {
29
+ const guidanceFiles = listRepositoryFiles().filter(isRequiredTestGuidance);
30
+ assert.ok(guidanceFiles.length > 0, 'No repository test guidance was found.');
31
+
32
+ for (const guidanceFile of guidanceFiles) {
33
+ const normalizedGuidance = readFileSync(guidanceFile, 'utf8').replace(/\s+/g, ' ').trim();
34
+ assert.ok(
35
+ normalizedGuidance.includes(TEST_ORDER_INVARIANCE_RULE),
36
+ `${guidanceFile} must document the non-negotiable test order invariance rule.`,
37
+ );
38
+ }
39
+ });
40
+
41
+ test('keeps default package scripts on the test framework ordinary ordering', () => {
42
+ const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
43
+
44
+ for (const [scriptName, command] of Object.entries(packageJson.scripts ?? {})) {
45
+ assert.equal(typeof command, 'string', `${scriptName} must be a string command.`);
46
+ assert.doesNotMatch(command, /--test-randomize|--test-random-seed/, `${scriptName} must not force randomized test ordering.`);
47
+ }
48
+ });
@@ -0,0 +1,39 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { join, relative, resolve } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const repositoryRoot = resolve(import.meta.dirname, '../..');
9
+ const packageJson = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
10
+ const testIndexSource = readFileSync(new URL('../../test/index.test.js', import.meta.url), 'utf8');
11
+ const { parallelTestFiles } = require('../../test/index.test.js');
12
+
13
+ function listRuntimeTestFiles(directoryPath) {
14
+ return readdirSync(directoryPath, { withFileTypes: true }).flatMap(entry => {
15
+ const entryPath = join(directoryPath, entry.name);
16
+ if (entry.isDirectory()) return listRuntimeTestFiles(entryPath);
17
+ if (!/\.test\.(?:js|mjs)$/.test(entry.name) || entry.name === 'index.test.js') {
18
+ return [];
19
+ }
20
+ return [relative(repositoryRoot, entryPath)];
21
+ });
22
+ }
23
+
24
+ test('keeps runtime test selection in the index and package scripts short', () => {
25
+ assert.equal(packageJson.scripts.test, 'npm run test:order && node test/index.test.js');
26
+ assert.match(packageJson.scripts['test:coverage'], /nyc node test\/index\.test\.js$/);
27
+ assert.match(testIndexSource, /parallelTestFiles/);
28
+ assert.match(testIndexSource, /runIndexedMochaTests/);
29
+ assert.match(testIndexSource, /if \(require\.main === module\)/);
30
+ });
31
+
32
+ test('runs the parallel execution contract in the test-order gate', () => {
33
+ assert.match(packageJson.scripts['test:order'], /scripts\/testParallel\/parallelTestPolicy\.test\.mjs/);
34
+ assert.match(packageJson.scripts['test:order'], /scripts\/testParallel\/runIndexedMochaTests\.test\.mjs/);
35
+ });
36
+
37
+ test('selects every runtime test file exactly once', () => {
38
+ assert.deepEqual([...parallelTestFiles].sort(), listRuntimeTestFiles(resolve(repositoryRoot, 'test')).sort());
39
+ });
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('node:child_process');
4
+ const { createRequire } = require('node:module');
5
+ const { availableParallelism } = require('node:os');
6
+ const { resolve } = require('node:path');
7
+
8
+ const DEFAULT_MAX_PARALLEL_JOBS = 4;
9
+
10
+ // Pattern: Configuration Boundary - bounds workers without accepting invalid input.
11
+ function resolveParallelJobCount(
12
+ configuredJobCount,
13
+ testFileCount,
14
+ defaultMaximum = DEFAULT_MAX_PARALLEL_JOBS,
15
+ availableJobCount = availableParallelism(),
16
+ ) {
17
+ const requestedJobCount =
18
+ configuredJobCount === undefined ? Math.min(availableJobCount, defaultMaximum) : Number.parseInt(configuredJobCount, 10);
19
+
20
+ if (!Number.isInteger(requestedJobCount) || requestedJobCount < 1) {
21
+ throw new Error('TEST_PARALLEL_JOBS must be a positive integer.');
22
+ }
23
+ return Math.min(requestedJobCount, testFileCount);
24
+ }
25
+
26
+ // Pattern: Command Builder - keeps Mocha worker details out of package metadata.
27
+ function buildMochaArguments(testFiles, jobCount) {
28
+ const requireFromRunner = createRequire(__filename);
29
+ return [
30
+ requireFromRunner.resolve('mocha/bin/mocha.js'),
31
+ '--parallel',
32
+ '--jobs',
33
+ String(jobCount),
34
+ '--require',
35
+ resolve('scripts/testOrder/randomizeTestOrder.cjs'),
36
+ ...testFiles,
37
+ ];
38
+ }
39
+
40
+ // Pattern: Process Adapter - returns the exact test process result to the index.
41
+ function runIndexedMochaTests(testFiles) {
42
+ if (testFiles.length === 0) {
43
+ throw new Error('The package test index must select at least one test file.');
44
+ }
45
+
46
+ const jobCount = resolveParallelJobCount(process.env.TEST_PARALLEL_JOBS, testFiles.length);
47
+ const child = spawn(process.execPath, buildMochaArguments(testFiles, jobCount), {
48
+ env: {
49
+ ...process.env,
50
+ NODE_ENV: 'test',
51
+ },
52
+ stdio: 'inherit',
53
+ });
54
+
55
+ return new Promise((resolveExit, rejectExit) => {
56
+ child.once('error', rejectExit);
57
+ child.once('exit', (code, signal) => {
58
+ if (signal) {
59
+ rejectExit(new Error(`Mocha exited from signal ${signal}.`));
60
+ return;
61
+ }
62
+ resolveExit(code ?? 1);
63
+ });
64
+ });
65
+ }
66
+
67
+ module.exports = {
68
+ buildMochaArguments,
69
+ resolveParallelJobCount,
70
+ runIndexedMochaTests,
71
+ };
@@ -0,0 +1,21 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createRequire } from 'node:module';
3
+ import test from 'node:test';
4
+
5
+ const require = createRequire(import.meta.url);
6
+ const { buildMochaArguments, resolveParallelJobCount } = require('./runIndexedMochaTests.cjs');
7
+
8
+ test('uses bounded Mocha file workers without randomized default ordering', () => {
9
+ assert.equal(resolveParallelJobCount(undefined, 8, 4, 12), 4);
10
+ assert.equal(resolveParallelJobCount('2', 8, 4, 12), 2);
11
+
12
+ const argumentsList = buildMochaArguments(['test/example.test.js'], 2);
13
+
14
+ assert.ok(argumentsList.includes('--parallel'));
15
+ assert.deepEqual(argumentsList.slice(argumentsList.indexOf('--jobs'), argumentsList.indexOf('--jobs') + 2), ['--jobs', '2']);
16
+ assert.ok(argumentsList.includes('test/example.test.js'));
17
+ });
18
+
19
+ test('rejects invalid worker configuration instead of changing execution silently', () => {
20
+ assert.throws(() => resolveParallelJobCount('0', 8, 4, 12), /TEST_PARALLEL_JOBS must be a positive integer/);
21
+ });