@carecard/validate 3.36.0 → 3.37.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.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: carecard-api-contract
3
+ description: 'Mandatory for every task after carecard-must-do and the repository engineering standards. Maintain non-negotiable API conformity across server calls, direct browser uploads and messaging, seed data, mocks, tests, and documentation.'
4
+ ---
5
+
6
+ # CareCard API Contract
7
+
8
+ ## Mandatory Use
9
+
10
+ Every agent must read and apply this skill on every task, including planning,
11
+ read-only investigation, coding, review, tests, documentation, and skill work.
12
+ Load `carecard-must-do` first, then the repository engineering standards, then
13
+ this skill before narrower skills. Use this repository's own copy; no test,
14
+ hook, build, or runtime dependency on another checkout is permitted.
15
+
16
+ ## Non-Negotiable API Conformity
17
+
18
+ The authoritative API contracts are the implemented request and response
19
+ boundaries between `ms-*` and `app-*` server components, called through the
20
+ `app-*` service layer. The implemented direct browser contracts for file
21
+ uploads and messaging are also authoritative.
22
+
23
+ Seed data in `cc-seed-data`, mock data and test data in every `app-*` repository,
24
+ API documentation, E2E test data, helpers, examples, and every other place
25
+ where these contracts are represented must always use and conform to those
26
+ authoritative contracts. A mock, seed, test, document, or example must never
27
+ redefine an API contract. This requirement is non-negotiable.
28
+
29
+ Every field in every non-authoritative request and response must have the same
30
+ data type and nullability as its authoritative boundary. Preserve UUID string
31
+ formats, timestamp and date-only string formats, booleans, numbers, arrays,
32
+ objects, and explicit null values. Preserve endpoint-specific numeric strings
33
+ when the owner returns them, including PostgreSQL BIGINT message sequences;
34
+ do not substitute numbers just because an app transform later parses them.
35
+
36
+ ## Determine the Contract Before Changing Representations
37
+
38
+ - Trace each request through the application service query, validation, and
39
+ transform to the owning microservice route, controller, and response writer.
40
+ Inspect database/model behavior when it determines the returned data.
41
+ - Compare method, path, authentication, parameters, headers, body fields,
42
+ casing, types, identifiers, required/optional/null values, status codes,
43
+ response data, errors, envelope metadata, and pagination.
44
+ - Distinguish raw HTTP data from transformed application objects and relational
45
+ seed rows. Seed rows must satisfy the owning schema and produce conforming
46
+ API results; they are not HTTP response envelopes.
47
+ - Preserve intentional mock-only controls as explicit development seams. They
48
+ do not authorize different production API payloads or outcomes.
49
+ - For direct browser uploads, trace the application-owned intent request and
50
+ the byte upload with its one-use bearer credential. Preserve the actual
51
+ multipart body, response, and failure contract.
52
+ - For direct browser messaging, trace application-owned ticket issuance,
53
+ handshake authentication, commands, acknowledgements, events, and recovery.
54
+ Preserve endpoint-specific formats; do not wrap binary content, bodyless
55
+ responses, tickets, or realtime acknowledgements in a generic JSON envelope.
56
+
57
+ ## Repair and Maintain All Affected Representations
58
+
59
+ - Fix verified drift at its originating input, generator, mock factory, or
60
+ fixture and update every dependent representation in the same task.
61
+ - Edit seed facts/generators in `cc-seed-data`; regenerate ignored outputs
62
+ rather than patching generated dumps or creating editable seed copies.
63
+ - Reuse existing service types and repository-native helpers. Do not add
64
+ compatibility aliases, alternate shapes, a parallel schema framework, or
65
+ runtime cross-repository imports to conceal a mismatch.
66
+ - When an authoritative contract intentionally evolves, identify and update
67
+ every affected seed, mock, test, example, API document, E2E scenario, and skill.
68
+ Documentation inventories remain derived evidence, not a new authority.
69
+ - Keep equivalent copies of this skill aligned and preserve repository-local
70
+ ownership. Do not infer authorization to publish, deploy, or reset data.
71
+
72
+ ## Behavioral Validation
73
+
74
+ Use TDD for executable changes: establish a failing observable request,
75
+ response, event, or persisted-data assertion before implementation. Exercise
76
+ public callable boundaries; do not test source text or internal structure.
77
+ Cover affected success and failure outcomes, and run the validation required
78
+ by the current task and the owning repository. Validate documentation and
79
+ skill changes with focused non-test checks. Never claim an unrun check passed.
80
+
81
+ ## Shared Engineering Requirements
82
+
83
+ 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, run the relevant focused non-test validation before changing the prose; do not add automated tests that inspect prose, files, or repository structure.
84
+
85
+ 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.
86
+
87
+ 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.
88
+
89
+ ### Non-Negotiable Microservice Layer Testing Contract
90
+
91
+ For every `ms-*` microservice, each applicable testing layer below is mandatory. In this contract, testing a function directly means invoking its documented callable boundary with controlled inputs. It never means testing its internals or other implementation details directly. Every function under test must be treated as a black box.
92
+
93
+ - Every database function and every stable database-access function must be exercised through its callable database boundary against the repository-owned test database. Place this coverage in `test/db/<related-model>[.<behavior>].test.<ext>`.
94
+ - Every model function that reads or changes database data must be exercised through its callable model interface. Place this coverage in `test/<sub-app>/model.test.<ext>` or a more specific model test file in the same directory.
95
+ - Every controller function used by the application must be exercised through its callable controller interface with controlled request, response, and error inputs. Place this coverage in `test/<sub-app>/controller.test.<ext>` or a more specific controller test file in the same directory.
96
+ - Every application route must be exercised with real HTTP requests against the composed application mounted on a repository-managed Node HTTP server. Place this coverage in `test/<sub-app>/app.test.<ext>`; use `test/app/app.test.<ext>` for routes without a sub-app.
97
+ - Every large helper function and every helper with independent domain behavior, validation, mapping, branching, state transition, object mutation, or failure behavior must have behavior coverage in `test/<sub-app>/helper.test.<ext>` or an appropriately named test file. Exercise a documented callable helper interface as a black box. Cover a private helper implementation through the callable boundary that owns it, and never export or invoke it solely for testing.
98
+ - Tests must provide inputs and assert observable outputs, errors, persisted state, side effects, emitted behavior, or intentional object mutation.
99
+ - Tests must not directly invoke, inspect, spy on, or assert implementation details, including private functions or state, SQL or query text and construction, algorithms, branches, control flow, variable names, collaborator sequences, or internal call counts.
100
+ - A behavior-preserving rewrite behind an unchanged callable contract must not require functional test changes.
101
+ - Use the repository-native test extension, such as `.js`, `.ts`, or `.mjs`. A microservice without a particular layer does not need an empty placeholder test file.
@@ -5,6 +5,15 @@ description: 'Use after carecard-must-do. Mandatory for every pkg-validate task,
5
5
 
6
6
  # Pkg Validate Coding Standards And Best Practices
7
7
 
8
+ ## Mandatory API Contract Skill
9
+
10
+ Non-negotiable: On every task, read and apply
11
+ [carecard-api-contract](../carecard-api-contract/SKILL.md) after
12
+ `carecard-must-do` and this engineering skill, before any narrower skill. This
13
+ applies to planning, read-only work, implementation, tests, documentation, and
14
+ skill maintenance. All non-authoritative representations must always conform
15
+ to the implemented server and direct browser API contracts.
16
+
8
17
  ## Release 1.1.1 and production data
9
18
 
10
19
  CareCard Release 1.1.1 is the production baseline. Seeding with
package/index.d.ts CHANGED
@@ -144,8 +144,10 @@ export const isCcIdString: BoolValidator;
144
144
  export const isCharactersString: BoolValidator;
145
145
  /** Checks if the string is a valid street address format. */
146
146
  export const isStreetString: BoolValidator;
147
- /** Checks if the string is a valid name format. */
147
+ /** Checks visible, well-formed Unicode names up to 1000 UTF-16 code units. */
148
148
  export const isNameString: BoolValidator;
149
+ /** Checks NFKC-trimmed filename metadata up to 255 code points, excluding paths and unsafe controls. */
150
+ export const isFileNameString: BoolValidator;
149
151
  /** Checks if the string is safe for search queries. */
150
152
  export const isSafeSearchString: BoolValidator;
151
153
  /** Checks if the string is a valid email address. */
@@ -203,6 +205,7 @@ export const validate: {
203
205
  isCharactersString: typeof isCharactersString;
204
206
  isStreetString: typeof isStreetString;
205
207
  isNameString: typeof isNameString;
208
+ isFileNameString: typeof isFileNameString;
206
209
  isSafeSearchString: typeof isSafeSearchString;
207
210
  isEmailString: typeof isEmailString;
208
211
  isJwtString: typeof isJwtString;
package/lib/validate.js CHANGED
@@ -68,11 +68,36 @@ const isStreetString = str => {
68
68
  return streetRegex.test(value);
69
69
  };
70
70
 
71
+ // Pattern: Predicate - shares Unicode safety without changing the original text.
72
+ const hasSafeVisibleName = value => {
73
+ return (
74
+ value.isWellFormed() &&
75
+ !/[\p{Cc}\p{Zl}\p{Zp}\u202a-\u202e\u2066-\u2069]/u.test(value) &&
76
+ /[\p{L}\p{N}\p{P}\p{S}\p{Co}]/u.test(value.replace(/\p{Default_Ignorable_Code_Point}/gu, ''))
77
+ );
78
+ };
79
+
80
+ // Pattern: Predicate - accepts visible international names within the existing size budget.
71
81
  const isNameString = str => {
72
82
  if (typeof str !== 'string' || str.length === 0 || str.length > 1000) {
73
83
  return false;
74
84
  }
75
- return /^[A-Za-z][0-9a-zA-Z-_.,'() ]+$/.test(str.trim());
85
+ return hasSafeVisibleName(str.trim());
86
+ };
87
+
88
+ // Pattern: Predicate - validates canonical display metadata, never a filesystem path.
89
+ const isFileNameString = str => {
90
+ if (typeof str !== 'string') {
91
+ return false;
92
+ }
93
+ const fileName = str.normalize('NFKC').trim();
94
+ return (
95
+ [...fileName].length <= 255 &&
96
+ hasSafeVisibleName(fileName) &&
97
+ fileName !== '.' &&
98
+ fileName !== '..' &&
99
+ !/[/\\]/u.test(fileName)
100
+ );
76
101
  };
77
102
 
78
103
  const isSafeSearchString = str => {
@@ -350,6 +375,7 @@ module.exports = {
350
375
  isCharactersString,
351
376
  isStreetString,
352
377
  isNameString,
378
+ isFileNameString,
353
379
  isSafeSearchString,
354
380
  isEmailString,
355
381
  isJwtString,
@@ -7,7 +7,7 @@ const {
7
7
  isCcIdString,
8
8
  validatePassword,
9
9
  isNameString,
10
- isSafeSearchString,
10
+ isFileNameString,
11
11
  isCharactersString,
12
12
  isValidIntegerString,
13
13
  isValidJsonString,
@@ -35,7 +35,14 @@ function validateProperties(obj = {}) {
35
35
  case 'firstName':
36
36
  case 'last_name':
37
37
  case 'lastName':
38
- case 'username':
38
+ case 'middle_name':
39
+ case 'middleName':
40
+ case 'nick_name':
41
+ case 'nickName':
42
+ case 'requested_by_name':
43
+ case 'requestedByName':
44
+ case 'approved_by_name':
45
+ case 'approvedByName':
39
46
  case 'new_status':
40
47
  case 'newStatus':
41
48
  case 'description':
@@ -79,6 +86,18 @@ function validateProperties(obj = {}) {
79
86
  returnObj[key] = value;
80
87
  }
81
88
  break;
89
+ case 'username':
90
+ if (isNameString(value) && /^[A-Za-z][0-9a-zA-Z-_.,'() ]+$/.test(value.trim())) {
91
+ returnObj[key] = value;
92
+ }
93
+ break;
94
+ case 'file_name':
95
+ case 'fileName':
96
+ case 'filename':
97
+ if (isFileNameString(value)) {
98
+ returnObj[key] = value.normalize('NFKC').trim();
99
+ }
100
+ break;
82
101
  case 'street':
83
102
  if (isStreetString(value)) {
84
103
  returnObj[key] = value;
@@ -101,7 +120,7 @@ function validateProperties(obj = {}) {
101
120
  break;
102
121
  case 'search_string':
103
122
  case 'searchString':
104
- if (isSafeSearchString(value)) {
123
+ if (isNameString(value) && value.trim().length <= 101) {
105
124
  returnObj[key] = value;
106
125
  }
107
126
  break;
@@ -205,16 +224,10 @@ function validateProperties(obj = {}) {
205
224
  returnObj[key] = value;
206
225
  }
207
226
  break;
208
- case 'requested_by_name':
209
- case 'requestedByName':
210
227
  case 'document_description':
211
228
  case 'documentDescription':
212
- case 'nick_name':
213
- case 'nickName':
214
229
  case 'requested_by_phone':
215
230
  case 'requestedByPhone':
216
- case 'approved_by_name':
217
- case 'approvedByName':
218
231
  case 'approved_by_phone':
219
232
  case 'approvedByPhone':
220
233
  if (isTextString(value)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/validate",
3
- "version": "3.36.0",
3
+ "version": "3.37.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/CareCard-ca/pkg-validate.git"
@@ -47,7 +47,7 @@
47
47
  "typescript": "6.0.3"
48
48
  },
49
49
  "dependencies": {
50
- "@carecard/common-util": "3.36.0"
50
+ "@carecard/common-util": "3.37.0"
51
51
  },
52
52
  "nyc": {
53
53
  "all": true,