@usefidel/contracts 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -31,6 +31,19 @@ import { RUN_ERROR_CODES, resolveRunDisplay } from '@usefidel/contracts';
31
31
  import { resolveRunDisplay } from '@usefidel/contracts/run-errors';
32
32
  ```
33
33
 
34
+ The canonical `fidel.config.json` builders live behind their own entry point, so
35
+ importing them is a deliberate act rather than a side effect of importing the
36
+ package:
37
+
38
+ ```js
39
+ import { buildDsConfigJson, nameFromUrl } from '@usefidel/contracts/onboarding-config';
40
+ ```
41
+
42
+ These are the ONE implementation of what `fidel init` writes. The CLI uses them
43
+ to write the file and the onboarding UI uses them to render the example a user
44
+ copies, so the two cannot drift. Their output formatting — two-space indent, key
45
+ order, trailing newline — is part of the contract and is asserted byte for byte.
46
+
34
47
  Ships ESM with TypeScript declarations. No runtime dependencies.
35
48
 
36
49
  ## Versioning
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Canonical `fidel.config.json` builders — the ONE implementation.
3
+ *
4
+ * WHY THIS LIVES IN THE CONTRACTS PACKAGE (F-10)
5
+ * ----------------------------------------------
6
+ * Two surfaces have to agree, byte for byte, about what `fidel init` writes:
7
+ *
8
+ * 1. the CLI, which writes the file, and
9
+ * 2. the onboarding UI in `usefidel/fidel-web`, which SHOWS the user that
10
+ * exact text to copy.
11
+ *
12
+ * They used to agree by accident. The builders lived in
13
+ * `github-action/src/init.ts`, and a test in the webapp imported them across a
14
+ * relative path to assert the displayed example matched. The frontend split
15
+ * broke that path — `github-action/` stayed in the monorepo — and the test was
16
+ * excluded rather than deleted so the loss stayed visible (finding F-10). From
17
+ * then until this module existed, the CLI and the onboarding example could
18
+ * diverge silently, and the failure would land on a new user copying a config
19
+ * that no longer works.
20
+ *
21
+ * Publishing the builders makes the agreement structural instead of hopeful:
22
+ *
23
+ * onboarding-config (here) -> fidel init (writes the file)
24
+ * onboarding-config (here) -> onboarding UI (shows + asserts the file)
25
+ *
26
+ * NOT a shared-utils dumping ground. This module holds only the pure logic both
27
+ * consumers need to produce identical config bytes. It has no imports, touches
28
+ * no I/O, and knows nothing about prompts, filesystems, or the matching engine.
29
+ * Anything that does not have to be identical across both repos does not belong
30
+ * here — it belongs in the consumer that needs it.
31
+ *
32
+ * Exposed under its own entry point (`@usefidel/contracts/onboarding-config`)
33
+ * rather than the package root, so importing it is a deliberate act.
34
+ *
35
+ * FORMATTING IS PART OF THE CONTRACT. Users copy this text by hand. Two-space
36
+ * indent, key order, and the trailing newline are all load-bearing, and the
37
+ * webapp asserts them byte for byte. Changing any of them is a breaking change
38
+ * to what people paste into their repositories.
39
+ */
40
+ /** One `designSystemChecks[]` entry. */
41
+ export interface DsSelection {
42
+ name: string;
43
+ url: string;
44
+ brandKey?: string;
45
+ }
46
+ /**
47
+ * Derive a check name from a URL.
48
+ *
49
+ * Same convention as the Figma branch's frame naming: `hostname/path`, falling
50
+ * back to the bare hostname when the path is empty, and to the raw input when
51
+ * the URL will not parse (a name is better than a crash during setup).
52
+ */
53
+ export declare function nameFromUrl(url: string): string;
54
+ /**
55
+ * Design-system branch: write or merge `designSystemChecks[]` alongside any
56
+ * existing `checks[]` (Figma) entries.
57
+ *
58
+ * Never touches `checks[]` — the two arrays are independent siblings per the
59
+ * `_shared/app-config.ts` contract, and the runner executes each leg
60
+ * separately. Entries are merged, deduplicating on the (name, url) pair.
61
+ *
62
+ * `checks` is emitted FIRST when present. That ordering is observable in the
63
+ * output the user copies, so it is fixed, not incidental.
64
+ */
65
+ export declare function buildDsConfigJson(existingRaw: Record<string, unknown> | null, dsSelections: DsSelection[]): string;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Canonical `fidel.config.json` builders — the ONE implementation.
3
+ *
4
+ * WHY THIS LIVES IN THE CONTRACTS PACKAGE (F-10)
5
+ * ----------------------------------------------
6
+ * Two surfaces have to agree, byte for byte, about what `fidel init` writes:
7
+ *
8
+ * 1. the CLI, which writes the file, and
9
+ * 2. the onboarding UI in `usefidel/fidel-web`, which SHOWS the user that
10
+ * exact text to copy.
11
+ *
12
+ * They used to agree by accident. The builders lived in
13
+ * `github-action/src/init.ts`, and a test in the webapp imported them across a
14
+ * relative path to assert the displayed example matched. The frontend split
15
+ * broke that path — `github-action/` stayed in the monorepo — and the test was
16
+ * excluded rather than deleted so the loss stayed visible (finding F-10). From
17
+ * then until this module existed, the CLI and the onboarding example could
18
+ * diverge silently, and the failure would land on a new user copying a config
19
+ * that no longer works.
20
+ *
21
+ * Publishing the builders makes the agreement structural instead of hopeful:
22
+ *
23
+ * onboarding-config (here) -> fidel init (writes the file)
24
+ * onboarding-config (here) -> onboarding UI (shows + asserts the file)
25
+ *
26
+ * NOT a shared-utils dumping ground. This module holds only the pure logic both
27
+ * consumers need to produce identical config bytes. It has no imports, touches
28
+ * no I/O, and knows nothing about prompts, filesystems, or the matching engine.
29
+ * Anything that does not have to be identical across both repos does not belong
30
+ * here — it belongs in the consumer that needs it.
31
+ *
32
+ * Exposed under its own entry point (`@usefidel/contracts/onboarding-config`)
33
+ * rather than the package root, so importing it is a deliberate act.
34
+ *
35
+ * FORMATTING IS PART OF THE CONTRACT. Users copy this text by hand. Two-space
36
+ * indent, key order, and the trailing newline are all load-bearing, and the
37
+ * webapp asserts them byte for byte. Changing any of them is a breaking change
38
+ * to what people paste into their repositories.
39
+ */
40
+ /**
41
+ * Derive a check name from a URL.
42
+ *
43
+ * Same convention as the Figma branch's frame naming: `hostname/path`, falling
44
+ * back to the bare hostname when the path is empty, and to the raw input when
45
+ * the URL will not parse (a name is better than a crash during setup).
46
+ */
47
+ export function nameFromUrl(url) {
48
+ try {
49
+ const parsed = new URL(url);
50
+ const path = parsed.pathname.replace(/^\/|\/$/g, "");
51
+ return path ? `${parsed.hostname}/${path}` : parsed.hostname;
52
+ }
53
+ catch {
54
+ return url;
55
+ }
56
+ }
57
+ /**
58
+ * Design-system branch: write or merge `designSystemChecks[]` alongside any
59
+ * existing `checks[]` (Figma) entries.
60
+ *
61
+ * Never touches `checks[]` — the two arrays are independent siblings per the
62
+ * `_shared/app-config.ts` contract, and the runner executes each leg
63
+ * separately. Entries are merged, deduplicating on the (name, url) pair.
64
+ *
65
+ * `checks` is emitted FIRST when present. That ordering is observable in the
66
+ * output the user copies, so it is fixed, not incidental.
67
+ */
68
+ export function buildDsConfigJson(existingRaw, dsSelections) {
69
+ const existingChecks = existingRaw?.checks;
70
+ const rawDs = existingRaw?.designSystemChecks;
71
+ const existingDsChecks = Array.isArray(rawDs) ? rawDs : [];
72
+ const merged = [...existingDsChecks];
73
+ for (const sel of dsSelections) {
74
+ if (!merged.some((c) => c.name === sel.name && c.url === sel.url)) {
75
+ merged.push(sel);
76
+ }
77
+ }
78
+ const out = {};
79
+ if (existingChecks !== undefined) {
80
+ out.checks = existingChecks;
81
+ }
82
+ out.designSystemChecks = merged;
83
+ return JSON.stringify(out, null, 2) + "\n";
84
+ }
@@ -15,7 +15,7 @@
15
15
  * When editing this file, run `npx tsx scripts/check-vendor-sync.ts` and
16
16
  * update all consumer copies.
17
17
  */
18
- export declare const RUN_ERROR_CODES: readonly ["TARGET_AUTH_WALL", "TARGET_UNREACHABLE", "TARGET_TIMEOUT", "TARGET_CSP_BLOCKED", "FIGMA_ACCESS_DENIED", "FIGMA_TOKEN_EXPIRED", "FIGMA_NOT_FOUND", "FIGMA_RATE_LIMITED", "PIPELINE_TIMEOUT", "PIPELINE_ERROR", "ZERO_ELEMENTS_MATCHED", "PERSIST_FAILED", "SESSION_EXPIRED", "INVALID_REFERENCE_URL", "INVALID_TARGET_URL", "DESIGN_SYSTEM_NOT_AVAILABLE", "DESIGN_SYSTEM_CONTEXT_NOT_AVAILABLE", "DESIGN_SYSTEM_INCOMPATIBLE", "FLOW_STEP_FAILED", "UNKNOWN_ERROR"];
18
+ export declare const RUN_ERROR_CODES: readonly ["TARGET_AUTH_WALL", "TARGET_UNREACHABLE", "TARGET_TIMEOUT", "TARGET_CSP_BLOCKED", "FIGMA_ACCESS_DENIED", "FIGMA_TOKEN_EXPIRED", "FIGMA_NOT_FOUND", "FIGMA_RATE_LIMITED", "PIPELINE_TIMEOUT", "PIPELINE_ERROR", "ZERO_ELEMENTS_MATCHED", "PERSIST_FAILED", "SESSION_EXPIRED", "INVALID_REFERENCE_URL", "INVALID_TARGET_URL", "DESIGN_SYSTEM_NOT_AVAILABLE", "DESIGN_SYSTEM_CONTEXT_NOT_AVAILABLE", "DESIGN_SYSTEM_INCOMPATIBLE", "FLOW_STEP_FAILED", "CAPABILITY_UNAVAILABLE", "PROVIDER_REQUEST_REJECTED", "UNKNOWN_ERROR"];
19
19
  export type RunErrorCode = (typeof RUN_ERROR_CODES)[number];
20
20
  export interface RunErrorMeta {
21
21
  retryable: boolean;
@@ -66,6 +66,23 @@ export const RUN_ERROR_CODES = [
66
66
  // (e.g. a transition target is missing or the step response is invalid).
67
67
  // retryable: false — this is a structural design problem, not a transient error.
68
68
  'FLOW_STEP_FAILED',
69
+ // Plan G-10 follow-up: replay-provider outcomes that cannot recover by
70
+ // retrying. Both are deliberately provider-NEUTRAL, in name and in message.
71
+ // The provider, its status codes and its response bodies stay in structured
72
+ // logs (`replay_provider_failed`); error_message is owner-readable AND
73
+ // projected through get_shared_run to anon share viewers, so it must not
74
+ // name a vendor or describe our configuration (same H-4 reasoning as the
75
+ // DESIGN_SYSTEM_* codes above).
76
+ //
77
+ // CAPABILITY_UNAVAILABLE — the saved-session capability itself is not
78
+ // usable: credential absent, rejected, or the plan/quota is exhausted.
79
+ // Operator-side. retryable: false — no number of retries configures a key.
80
+ 'CAPABILITY_UNAVAILABLE',
81
+ // PROVIDER_REQUEST_REJECTED — a deterministic rejection of this specific
82
+ // request (a 4xx that is not auth, not "gone", not rate limiting), or a 2xx
83
+ // whose body was structurally unusable. retryable: false — the same request
84
+ // will be rejected the same way.
85
+ 'PROVIDER_REQUEST_REJECTED',
69
86
  'UNKNOWN_ERROR',
70
87
  ];
71
88
  export const ERROR_CODE_META = {
@@ -134,6 +151,16 @@ export const ERROR_CODE_META = {
134
151
  userMessage: "This site's saved session has expired. Reconnect it to validate again.",
135
152
  shortLabel: 'Session expired',
136
153
  },
154
+ CAPABILITY_UNAVAILABLE: {
155
+ retryable: false,
156
+ userMessage: "Validating sites that need a saved sign-in isn't available right now. This one's on us — contact support and we'll get it working.",
157
+ shortLabel: 'Capability unavailable',
158
+ },
159
+ PROVIDER_REQUEST_REJECTED: {
160
+ retryable: false,
161
+ userMessage: "This validation couldn't be completed. Trying again won't change the result — contact support if you need it looked at.",
162
+ shortLabel: 'Validation rejected',
163
+ },
137
164
  INVALID_REFERENCE_URL: {
138
165
  retryable: false,
139
166
  userMessage: 'The reference URL is invalid. It must start with https:// and point to a real page.',
package/package.json CHANGED
@@ -7,8 +7,8 @@
7
7
  "The source of truth still lives in the monorepo at packages/contracts/."
8
8
  ],
9
9
  "name": "@usefidel/contracts",
10
- "version": "0.2.0",
11
- "description": "Shared, code-free contracts between Fidel surfaces. Run-error taxonomy and theme-intake wire types.",
10
+ "version": "0.4.0",
11
+ "description": "Shared, code-free contracts between Fidel surfaces. Run-error taxonomy, theme-intake wire types, and the canonical fidel.config.json builders.",
12
12
  "license": "UNLICENSED",
13
13
  "private": false,
14
14
  "type": "module",
@@ -27,6 +27,10 @@
27
27
  "./theme-intake": {
28
28
  "types": "./dist/theme-intake.d.ts",
29
29
  "import": "./dist/theme-intake.js"
30
+ },
31
+ "./onboarding-config": {
32
+ "types": "./dist/onboarding-config.d.ts",
33
+ "import": "./dist/onboarding-config.js"
30
34
  }
31
35
  },
32
36
  "files": [