@fireweaveai/server-sdk-cjs 0.0.0-reserve.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/application/client.d.ts +77 -0
  4. package/dist/application/client.js +144 -0
  5. package/dist/application/mode.d.ts +70 -0
  6. package/dist/application/mode.js +74 -0
  7. package/dist/application/ports.d.ts +94 -0
  8. package/dist/application/ports.js +2 -0
  9. package/dist/application/runtime.d.ts +101 -0
  10. package/dist/application/runtime.js +346 -0
  11. package/dist/domain/context.d.ts +67 -0
  12. package/dist/domain/context.js +90 -0
  13. package/dist/domain/errors.d.ts +45 -0
  14. package/dist/domain/errors.js +99 -0
  15. package/dist/domain/target.d.ts +2 -0
  16. package/dist/domain/target.js +2 -0
  17. package/dist/domain/types.d.ts +107 -0
  18. package/dist/domain/types.js +2 -0
  19. package/dist/domain/validation.d.ts +124 -0
  20. package/dist/domain/validation.js +355 -0
  21. package/dist/index.d.ts +34 -0
  22. package/dist/index.js +52 -0
  23. package/dist/infrastructure/adapters/inmemory.d.ts +77 -0
  24. package/dist/infrastructure/adapters/inmemory.js +151 -0
  25. package/dist/infrastructure/adapters/local.d.ts +108 -0
  26. package/dist/infrastructure/adapters/local.js +105 -0
  27. package/dist/infrastructure/adapters/remote.d.ts +74 -0
  28. package/dist/infrastructure/adapters/remote.js +373 -0
  29. package/dist/infrastructure/hosts.d.ts +18 -0
  30. package/dist/infrastructure/hosts.js +72 -0
  31. package/dist/register-browser.d.ts +1 -0
  32. package/dist/register-browser.js +7 -0
  33. package/dist/register.d.ts +1 -0
  34. package/dist/register.js +13 -0
  35. package/dist/start/browser.d.ts +12 -0
  36. package/dist/start/browser.js +36 -0
  37. package/dist/start/build-info.d.ts +2 -0
  38. package/dist/start/build-info.js +9 -0
  39. package/dist/start/control-points.d.ts +20 -0
  40. package/dist/start/control-points.js +59 -0
  41. package/dist/start/env.d.ts +11 -0
  42. package/dist/start/env.js +64 -0
  43. package/dist/start/fw.d.ts +29 -0
  44. package/dist/start/fw.js +76 -0
  45. package/dist/start/index.d.ts +23 -0
  46. package/dist/start/index.js +28 -0
  47. package/dist/start/instance.d.ts +17 -0
  48. package/dist/start/instance.js +56 -0
  49. package/dist/start/names.d.ts +22 -0
  50. package/dist/start/names.js +25 -0
  51. package/dist/start/resolve.d.ts +40 -0
  52. package/dist/start/resolve.js +139 -0
  53. package/dist/start/state.d.ts +96 -0
  54. package/dist/start/state.js +303 -0
  55. package/package.json +58 -0
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initFireweave = exports.FireweaveClient = exports.DEFAULT_SHUTDOWN_TIMEOUT_MS = exports.stableStringify = exports.FireweaveRuntime = exports.isLoopbackHostname = exports.assertHostAllowed = exports.DEFAULT_ALLOWED_HOSTS = exports.FireweaveLocalAdapter = exports.FireweaveRemoteAdapter = exports.InMemoryAdapter = exports.validateInitOptions = exports.validateTargetingKey = exports.validateContext = exports.validateDefaultValue = exports.validateControlPointKey = exports.canonicalizeContext = exports.resolvedContextView = exports.normalizeContextInput = exports.mergeContexts = exports.ALLOWED_FIREWEAVE_CONTEXT_KEYS = exports.DEFAULT_RESERVED_ATTRIBUTE_KEYS = exports.DEFAULT_CONTEXT_LIMITS = exports.isFireweaveError = exports.redactSecrets = exports.ERROR_TAXONOMY = exports.FireweaveError = void 0;
4
+ /**
5
+ * @fireweaveai/server-sdk — Fireweave release-engineering SDK for server runtimes
6
+ * (Node, Bun, Deno).
7
+ *
8
+ * Exactly two v1 capabilities (spec/control-points.md): control points and
9
+ * target registration.
10
+ *
11
+ * Production default: {@link FireweaveRemoteAdapter} (Fireweave project key →
12
+ * fw-server). {@link InMemoryAdapter} for offline work and tests. The backend
13
+ * fw-server forwards to is fw-server's concern — no vendor SDK, key, or host
14
+ * appears in the application process (ADR-0005, ADR-0006).
15
+ */
16
+ var errors_js_1 = require("./domain/errors.js");
17
+ Object.defineProperty(exports, "FireweaveError", { enumerable: true, get: function () { return errors_js_1.FireweaveError; } });
18
+ Object.defineProperty(exports, "ERROR_TAXONOMY", { enumerable: true, get: function () { return errors_js_1.ERROR_TAXONOMY; } });
19
+ Object.defineProperty(exports, "redactSecrets", { enumerable: true, get: function () { return errors_js_1.redactSecrets; } });
20
+ Object.defineProperty(exports, "isFireweaveError", { enumerable: true, get: function () { return errors_js_1.isFireweaveError; } });
21
+ var context_js_1 = require("./domain/context.js");
22
+ Object.defineProperty(exports, "DEFAULT_CONTEXT_LIMITS", { enumerable: true, get: function () { return context_js_1.DEFAULT_CONTEXT_LIMITS; } });
23
+ Object.defineProperty(exports, "DEFAULT_RESERVED_ATTRIBUTE_KEYS", { enumerable: true, get: function () { return context_js_1.DEFAULT_RESERVED_ATTRIBUTE_KEYS; } });
24
+ Object.defineProperty(exports, "ALLOWED_FIREWEAVE_CONTEXT_KEYS", { enumerable: true, get: function () { return context_js_1.ALLOWED_FIREWEAVE_CONTEXT_KEYS; } });
25
+ Object.defineProperty(exports, "mergeContexts", { enumerable: true, get: function () { return context_js_1.mergeContexts; } });
26
+ Object.defineProperty(exports, "normalizeContextInput", { enumerable: true, get: function () { return context_js_1.normalizeContextInput; } });
27
+ Object.defineProperty(exports, "resolvedContextView", { enumerable: true, get: function () { return context_js_1.resolvedContextView; } });
28
+ var validation_js_1 = require("./domain/validation.js");
29
+ Object.defineProperty(exports, "canonicalizeContext", { enumerable: true, get: function () { return validation_js_1.canonicalizeContext; } });
30
+ Object.defineProperty(exports, "validateControlPointKey", { enumerable: true, get: function () { return validation_js_1.validateControlPointKey; } });
31
+ Object.defineProperty(exports, "validateDefaultValue", { enumerable: true, get: function () { return validation_js_1.validateDefaultValue; } });
32
+ Object.defineProperty(exports, "validateContext", { enumerable: true, get: function () { return validation_js_1.validateContext; } });
33
+ Object.defineProperty(exports, "validateTargetingKey", { enumerable: true, get: function () { return validation_js_1.validateTargetingKey; } });
34
+ Object.defineProperty(exports, "validateInitOptions", { enumerable: true, get: function () { return validation_js_1.validateInitOptions; } });
35
+ var inmemory_js_1 = require("./infrastructure/adapters/inmemory.js");
36
+ Object.defineProperty(exports, "InMemoryAdapter", { enumerable: true, get: function () { return inmemory_js_1.InMemoryAdapter; } });
37
+ var remote_js_1 = require("./infrastructure/adapters/remote.js");
38
+ Object.defineProperty(exports, "FireweaveRemoteAdapter", { enumerable: true, get: function () { return remote_js_1.FireweaveRemoteAdapter; } });
39
+ var local_js_1 = require("./infrastructure/adapters/local.js");
40
+ Object.defineProperty(exports, "FireweaveLocalAdapter", { enumerable: true, get: function () { return local_js_1.FireweaveLocalAdapter; } });
41
+ var hosts_js_1 = require("./infrastructure/hosts.js");
42
+ Object.defineProperty(exports, "DEFAULT_ALLOWED_HOSTS", { enumerable: true, get: function () { return hosts_js_1.DEFAULT_ALLOWED_HOSTS; } });
43
+ Object.defineProperty(exports, "assertHostAllowed", { enumerable: true, get: function () { return hosts_js_1.assertHostAllowed; } });
44
+ Object.defineProperty(exports, "isLoopbackHostname", { enumerable: true, get: function () { return hosts_js_1.isLoopbackHostname; } });
45
+ var runtime_js_1 = require("./application/runtime.js");
46
+ Object.defineProperty(exports, "FireweaveRuntime", { enumerable: true, get: function () { return runtime_js_1.FireweaveRuntime; } });
47
+ Object.defineProperty(exports, "stableStringify", { enumerable: true, get: function () { return runtime_js_1.stableStringify; } });
48
+ Object.defineProperty(exports, "DEFAULT_SHUTDOWN_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_js_1.DEFAULT_SHUTDOWN_TIMEOUT_MS; } });
49
+ var client_js_1 = require("./application/client.js");
50
+ Object.defineProperty(exports, "FireweaveClient", { enumerable: true, get: function () { return client_js_1.FireweaveClient; } });
51
+ var mode_js_1 = require("./application/mode.js");
52
+ Object.defineProperty(exports, "initFireweave", { enumerable: true, get: function () { return mode_js_1.initFireweave; } });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Deterministic in-memory adapter for tests and conformance fixtures.
3
+ * Resolution model mirrors contracts/ fixture flag definitions: flags, variants,
4
+ * payloads, targeting by targeting key / person attributes / groups.
5
+ * Deliberately NO bucketing / percentage logic.
6
+ */
7
+ import { type FireweaveErrorKind } from '../../domain/errors.js';
8
+ import type { AdapterResolution, AdapterRuntimeFeatures, BackendAdapter, ResolveOptions } from '../../application/ports.js';
9
+ import type { CanonicalContext, DecisionReason, Exposure, JsonValue, Signal } from '../../domain/types.js';
10
+ export interface InMemoryFlagDefinition {
11
+ type: 'boolean' | 'string' | 'integer' | 'float' | 'object';
12
+ enabled: boolean;
13
+ value: JsonValue;
14
+ variant?: string;
15
+ variants?: string[];
16
+ payload?: JsonValue;
17
+ reason?: {
18
+ code?: string;
19
+ condition_index?: number;
20
+ description?: string;
21
+ };
22
+ metadata?: {
23
+ version?: number;
24
+ id?: number;
25
+ };
26
+ /** Canonical reason override (fixtures: "SPLIT"). */
27
+ fireweaveReason?: DecisionReason;
28
+ /** Served from last-good cache (stale scenarios). */
29
+ fromCache?: boolean;
30
+ /** Match only when all listed attributes deep-equal context attributes. */
31
+ matchAttribute?: Record<string, JsonValue>;
32
+ /** Match only when context groups contain these entries. */
33
+ matchGroups?: Record<string, string>;
34
+ /** Match only when person attributes contain these entries. */
35
+ matchPerson?: Record<string, JsonValue>;
36
+ /** Match only this exact targetingKey. */
37
+ matchTargetingKey?: string;
38
+ }
39
+ export interface InMemoryFault {
40
+ kind: FireweaveErrorKind;
41
+ metadata?: Record<string, string | number | boolean>;
42
+ }
43
+ export interface InMemoryAdapterOptions {
44
+ flags?: Record<string, InMemoryFlagDefinition>;
45
+ /** Fault to throw on every resolve (fault-mode conformance without HTTP). */
46
+ fault?: InMemoryFault;
47
+ /** Reject initialize() with this error kind. */
48
+ initError?: FireweaveErrorKind;
49
+ /** Delay initialize() until release() is called (cold-start tests). */
50
+ initGate?: {
51
+ promise: Promise<void>;
52
+ };
53
+ }
54
+ export declare class InMemoryAdapter implements BackendAdapter {
55
+ readonly name: "inmemory";
56
+ private flags;
57
+ private fault;
58
+ private readonly initError;
59
+ private readonly initGate;
60
+ private readonly exposures;
61
+ private readonly signals;
62
+ private closed;
63
+ constructor(options?: InMemoryAdapterOptions);
64
+ setFlags(flags: Record<string, InMemoryFlagDefinition>): void;
65
+ setFault(fault: InMemoryFault | undefined): void;
66
+ initialize(signal?: AbortSignal): Promise<void>;
67
+ resolve(controlPointKey: string, context: CanonicalContext, _options?: ResolveOptions): Promise<AdapterResolution>;
68
+ private matches;
69
+ recordExposure(exposure: Exposure): void;
70
+ getExposures(): readonly Exposure[];
71
+ recordSignal(signal: Signal): void;
72
+ getSignals(): readonly Signal[];
73
+ flush(): Promise<void>;
74
+ shutdown(): Promise<void>;
75
+ isClosed(): boolean;
76
+ features(): AdapterRuntimeFeatures;
77
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryAdapter = void 0;
4
+ /**
5
+ * Deterministic in-memory adapter for tests and conformance fixtures.
6
+ * Resolution model mirrors contracts/ fixture flag definitions: flags, variants,
7
+ * payloads, targeting by targeting key / person attributes / groups.
8
+ * Deliberately NO bucketing / percentage logic.
9
+ */
10
+ const errors_js_1 = require("../../domain/errors.js");
11
+ function deepEqual(a, b) {
12
+ if (a === b)
13
+ return true;
14
+ if (a === undefined || b === undefined || a === null || b === null)
15
+ return false;
16
+ if (typeof a !== typeof b)
17
+ return false;
18
+ if (Array.isArray(a) && Array.isArray(b)) {
19
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
20
+ }
21
+ if (typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
22
+ const ak = Object.keys(a);
23
+ const bk = Object.keys(b);
24
+ return ak.length === bk.length && ak.every((k) => deepEqual(a[k], b[k]));
25
+ }
26
+ return false;
27
+ }
28
+ class InMemoryAdapter {
29
+ name = 'inmemory';
30
+ flags;
31
+ fault;
32
+ initError;
33
+ initGate;
34
+ exposures = [];
35
+ signals = [];
36
+ closed = false;
37
+ constructor(options = {}) {
38
+ this.flags = new Map(Object.entries(options.flags ?? {}));
39
+ this.fault = options.fault;
40
+ this.initError = options.initError;
41
+ this.initGate = options.initGate;
42
+ }
43
+ setFlags(flags) {
44
+ this.flags = new Map(Object.entries(flags));
45
+ }
46
+ setFault(fault) {
47
+ this.fault = fault;
48
+ }
49
+ async initialize(signal) {
50
+ if (this.initError !== undefined) {
51
+ throw new errors_js_1.FireweaveError(this.initError);
52
+ }
53
+ if (this.initGate !== undefined) {
54
+ if (signal?.aborted)
55
+ throw new errors_js_1.FireweaveError('Timeout');
56
+ await this.initGate.promise;
57
+ }
58
+ }
59
+ async resolve(controlPointKey, context, _options) {
60
+ if (this.fault !== undefined) {
61
+ const opts = this.fault.metadata !== undefined ? { metadata: this.fault.metadata } : {};
62
+ throw new errors_js_1.FireweaveError(this.fault.kind, opts);
63
+ }
64
+ const def = this.flags.get(controlPointKey);
65
+ if (def === undefined) {
66
+ return { found: false };
67
+ }
68
+ if (!this.matches(def, context)) {
69
+ return { found: false };
70
+ }
71
+ const resolution = {
72
+ found: true,
73
+ enabled: def.enabled,
74
+ value: def.value,
75
+ flagType: def.type,
76
+ };
77
+ if (def.variant !== undefined)
78
+ resolution.variant = def.variant;
79
+ if (def.fireweaveReason !== undefined)
80
+ resolution.reason = def.fireweaveReason;
81
+ if (def.reason?.code !== undefined)
82
+ resolution.reasonCode = def.reason.code;
83
+ if (def.reason?.condition_index !== undefined)
84
+ resolution.conditionIndex = def.reason.condition_index;
85
+ if (def.metadata?.version !== undefined)
86
+ resolution.version = def.metadata.version;
87
+ if (def.metadata?.id !== undefined)
88
+ resolution.vendorControlPointId = def.metadata.id;
89
+ if (def.payload !== undefined)
90
+ resolution.payload = def.payload;
91
+ if (def.fromCache === true)
92
+ resolution.fromCache = true;
93
+ return resolution;
94
+ }
95
+ matches(def, context) {
96
+ if (def.matchTargetingKey !== undefined && context.targetingKey !== def.matchTargetingKey) {
97
+ return false;
98
+ }
99
+ if (def.matchAttribute !== undefined) {
100
+ for (const [k, v] of Object.entries(def.matchAttribute)) {
101
+ if (!deepEqual(context.attributes[k], v))
102
+ return false;
103
+ }
104
+ }
105
+ if (def.matchPerson !== undefined) {
106
+ for (const [k, v] of Object.entries(def.matchPerson)) {
107
+ if (!deepEqual(context.attributes[k], v))
108
+ return false;
109
+ }
110
+ }
111
+ if (def.matchGroups !== undefined) {
112
+ for (const [k, v] of Object.entries(def.matchGroups)) {
113
+ if (context.groups?.[k] !== v)
114
+ return false;
115
+ }
116
+ }
117
+ return true;
118
+ }
119
+ recordExposure(exposure) {
120
+ this.exposures.push(exposure);
121
+ }
122
+ getExposures() {
123
+ return this.exposures;
124
+ }
125
+ recordSignal(signal) {
126
+ this.signals.push(signal);
127
+ }
128
+ getSignals() {
129
+ return this.signals;
130
+ }
131
+ async flush() {
132
+ // no-op: exposures stay queryable for tests
133
+ }
134
+ async shutdown() {
135
+ this.closed = true;
136
+ }
137
+ isClosed() {
138
+ return this.closed;
139
+ }
140
+ features() {
141
+ return {
142
+ remoteEvaluation: false,
143
+ localEvaluation: true,
144
+ localOnly: true,
145
+ exposureEmission: true,
146
+ sideEffectFreeReads: true,
147
+ groupAnalytics: true,
148
+ };
149
+ }
150
+ }
151
+ exports.InMemoryAdapter = InMemoryAdapter;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Local development adapter — the DEV substrate for a scaffolded harness.
3
+ *
4
+ * It is the counterpart to {@link FireweaveRemoteAdapter}: prod evaluates
5
+ * control points against fw-server, dev evaluates them here, in-process, with
6
+ * no network and no credentials. Because it satisfies the same
7
+ * {@link BackendAdapter} port, the dev branch of a harness runs through the
8
+ * same {@link FireweaveRuntime} as prod — inheriting identical lifecycle
9
+ * gating and context canonicalization. That symmetry is the point: dev/prod
10
+ * skew in the harness is exactly what the harness exists to prevent.
11
+ *
12
+ * Resolution policy is deliberately minimal:
13
+ *
14
+ * - a key present in `devFlags` resolves to its mapped value with reason
15
+ * `STATIC` — the only supported way to turn a control point ON (or force it
16
+ * OFF) on a laptop;
17
+ * - every other key MISSES (`{ found: false, reason: 'DEFAULT' }`), which the
18
+ * runtime turns into the caller's own default with reason `DEFAULT` — not
19
+ * an error (spec/modes.md "Behaviour per mode": local's unknown-key row is
20
+ * deliberately `default`/`DEFAULT`, unlike remote's `default`/`ERROR`/
21
+ * `ControlPointNotFound`).
22
+ *
23
+ * Call-site defaults stay `false` under RAMP-1. Never write
24
+ * `fw.flag(key, true)` to dogfood locally — that same `true` is the production
25
+ * fallback when the control point is absent from the provider, so it silently
26
+ * ships the ON branch. Use `devFlags` instead.
27
+ *
28
+ * Runtime portability: pure computation, no I/O, no env, no Node globals — so
29
+ * it runs unchanged on Node, Bun, and Deno (ADR-0008), and is covered by
30
+ * `test/unit/runtime-portability.test.ts`.
31
+ */
32
+ import type { AdapterResolution, AdapterRuntimeFeatures, BackendAdapter, RegisterTargetOptions, RegisterTargetResult, ResolveOptions } from '../../application/ports.js';
33
+ import type { TargetKind } from '../../domain/target.js';
34
+ import type { CanonicalContext } from '../../domain/types.js';
35
+ export interface FireweaveLocalAdapterOptions {
36
+ /**
37
+ * Per-key boolean overrides. A present key resolves to its mapped value with
38
+ * reason `STATIC`; an absent key misses so the caller's default is used.
39
+ */
40
+ readonly devFlags?: Record<string, boolean>;
41
+ /**
42
+ * Sink for the local `registerTarget` trace. Defaults to `console.info`.
43
+ * Injectable so tests assert the call without capturing stdout, and so a
44
+ * host that owns its logging can route it.
45
+ */
46
+ readonly log?: (message: string) => void;
47
+ }
48
+ /** A target recorded by {@link FireweaveLocalAdapter.registerTarget}. */
49
+ export interface LocalRegisteredTarget {
50
+ readonly targetingKey: string;
51
+ readonly kind: TargetKind;
52
+ readonly properties: Record<string, unknown>;
53
+ readonly environment?: string;
54
+ }
55
+ export declare class FireweaveLocalAdapter implements BackendAdapter {
56
+ /**
57
+ * Not `inmemory` — that name belongs to {@link InMemoryAdapter}, the fixture
58
+ * adapter conformance runs against. This is a distinct dev substrate, and
59
+ * `capabilities.get()` should report it as such rather than impersonate one.
60
+ */
61
+ readonly name: "other";
62
+ private readonly devFlags;
63
+ private readonly log;
64
+ private readonly targets;
65
+ private closed;
66
+ constructor(options?: FireweaveLocalAdapterOptions);
67
+ initialize(_signal?: AbortSignal): Promise<void>;
68
+ /**
69
+ * A `devFlags` hit reports `enabled: true` alongside `reason: 'STATIC'`.
70
+ * Reporting `enabled: false` for an override of `false` would make the
71
+ * runtime label the decision `DISABLED` (runtime.ts, reason resolution),
72
+ * which means "the control point exists but is switched off upstream" — not
73
+ * what a local override expresses.
74
+ *
75
+ * `flagType: 'boolean'` is declared honestly. Reading an overridden key as a
76
+ * string or number therefore yields TYPE_MISMATCH rather than silently
77
+ * handing back the default: `devFlags` is `Record<string, boolean>`, so such
78
+ * a read is a genuine call-site mistake and is better surfaced than hidden.
79
+ *
80
+ * A miss carries `reason: 'DEFAULT'` on the `{ found: false }` resolution —
81
+ * the signal `FireweaveRuntime.evaluate` (runtime.ts) reads to return the
82
+ * caller's default with reason `DEFAULT` instead of falling through to its
83
+ * generic ControlPointNotFound/ERROR mapping (spec/modes.md).
84
+ */
85
+ resolve(controlPointKey: string, _context: CanonicalContext, _options?: ResolveOptions): Promise<AdapterResolution>;
86
+ /**
87
+ * Records the target in-process and traces it, rather than reporting
88
+ * `UnsupportedCapability`.
89
+ *
90
+ * The capability previously reported Unsupported so that a dev harness could
91
+ * not *silently* look registered — the failure being guarded against is a
92
+ * developer believing their targeting works because nothing objected. A
93
+ * recorded target plus an explicit `[fireweave:local]` line preserves that
94
+ * guarantee by a different route: nothing is silent, and local dev can now
95
+ * exercise targeting rules offline instead of only in production.
96
+ *
97
+ * The trace names the mode, so a line appearing in a production log is
98
+ * itself the signal that something booted in local mode by mistake.
99
+ *
100
+ * No network call is made and nothing reaches fw-server.
101
+ */
102
+ registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
103
+ /** Targets recorded this process, for assertions and dev inspection. */
104
+ getRegisteredTargets(): readonly LocalRegisteredTarget[];
105
+ shutdown(): Promise<void>;
106
+ isClosed(): boolean;
107
+ features(): AdapterRuntimeFeatures;
108
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FireweaveLocalAdapter = void 0;
4
+ class FireweaveLocalAdapter {
5
+ /**
6
+ * Not `inmemory` — that name belongs to {@link InMemoryAdapter}, the fixture
7
+ * adapter conformance runs against. This is a distinct dev substrate, and
8
+ * `capabilities.get()` should report it as such rather than impersonate one.
9
+ */
10
+ name = 'other';
11
+ devFlags;
12
+ log;
13
+ targets = new Map();
14
+ closed = false;
15
+ constructor(options = {}) {
16
+ this.devFlags = { ...(options.devFlags ?? {}) };
17
+ this.log = options.log ?? ((m) => console.info(m));
18
+ }
19
+ async initialize(_signal) {
20
+ // Nothing to connect to. Kept async to satisfy the port.
21
+ }
22
+ /**
23
+ * A `devFlags` hit reports `enabled: true` alongside `reason: 'STATIC'`.
24
+ * Reporting `enabled: false` for an override of `false` would make the
25
+ * runtime label the decision `DISABLED` (runtime.ts, reason resolution),
26
+ * which means "the control point exists but is switched off upstream" — not
27
+ * what a local override expresses.
28
+ *
29
+ * `flagType: 'boolean'` is declared honestly. Reading an overridden key as a
30
+ * string or number therefore yields TYPE_MISMATCH rather than silently
31
+ * handing back the default: `devFlags` is `Record<string, boolean>`, so such
32
+ * a read is a genuine call-site mistake and is better surfaced than hidden.
33
+ *
34
+ * A miss carries `reason: 'DEFAULT'` on the `{ found: false }` resolution —
35
+ * the signal `FireweaveRuntime.evaluate` (runtime.ts) reads to return the
36
+ * caller's default with reason `DEFAULT` instead of falling through to its
37
+ * generic ControlPointNotFound/ERROR mapping (spec/modes.md).
38
+ */
39
+ async resolve(controlPointKey, _context, _options) {
40
+ const override = this.devFlags[controlPointKey];
41
+ if (override === undefined)
42
+ return { found: false, reason: 'DEFAULT' };
43
+ return {
44
+ found: true,
45
+ enabled: true,
46
+ value: override,
47
+ variant: override ? 'on' : 'off',
48
+ reason: 'STATIC',
49
+ flagType: 'boolean',
50
+ };
51
+ }
52
+ /**
53
+ * Records the target in-process and traces it, rather than reporting
54
+ * `UnsupportedCapability`.
55
+ *
56
+ * The capability previously reported Unsupported so that a dev harness could
57
+ * not *silently* look registered — the failure being guarded against is a
58
+ * developer believing their targeting works because nothing objected. A
59
+ * recorded target plus an explicit `[fireweave:local]` line preserves that
60
+ * guarantee by a different route: nothing is silent, and local dev can now
61
+ * exercise targeting rules offline instead of only in production.
62
+ *
63
+ * The trace names the mode, so a line appearing in a production log is
64
+ * itself the signal that something booted in local mode by mistake.
65
+ *
66
+ * No network call is made and nothing reaches fw-server.
67
+ */
68
+ async registerTarget(targetingKey, options = {}) {
69
+ const kind = options.kind ?? 'user';
70
+ const properties = { ...(options.properties ?? {}) };
71
+ const target = {
72
+ targetingKey,
73
+ kind,
74
+ properties,
75
+ ...(options.environment === undefined ? {} : { environment: options.environment }),
76
+ };
77
+ this.targets.set(targetingKey, target);
78
+ this.log(`[fireweave:local] registerTarget ${kind} ${targetingKey} ` +
79
+ `${JSON.stringify(properties)} — recorded in-process, NOT sent to fw-server`);
80
+ return { ok: true };
81
+ }
82
+ /** Targets recorded this process, for assertions and dev inspection. */
83
+ getRegisteredTargets() {
84
+ return [...this.targets.values()];
85
+ }
86
+ async shutdown() {
87
+ this.closed = true;
88
+ }
89
+ isClosed() {
90
+ return this.closed;
91
+ }
92
+ features() {
93
+ return {
94
+ remoteEvaluation: false,
95
+ localEvaluation: true,
96
+ localOnly: true,
97
+ // No exposure sink exists locally; claiming otherwise would make
98
+ // capabilities.get() advertise emission that silently goes nowhere.
99
+ exposureEmission: false,
100
+ sideEffectFreeReads: true,
101
+ groupAnalytics: false,
102
+ };
103
+ }
104
+ }
105
+ exports.FireweaveLocalAdapter = FireweaveLocalAdapter;
@@ -0,0 +1,74 @@
1
+ import type { AdapterResolution, AdapterRuntimeFeatures, BackendAdapter, RegisterTargetOptions, RegisterTargetResult, ResolveOptions } from '../../application/ports.js';
2
+ import type { CanonicalContext, Exposure, Signal } from '../../domain/types.js';
3
+ type FetchLike = (url: string, init?: {
4
+ method?: string;
5
+ headers?: Record<string, string>;
6
+ body?: string;
7
+ signal?: AbortSignal;
8
+ }) => Promise<{
9
+ status: number;
10
+ text(): Promise<string>;
11
+ json(): Promise<unknown>;
12
+ }>;
13
+ export interface FireweaveRemoteAdapterOptions {
14
+ /**
15
+ * fw-server base URL (e.g. https://fw.example.com or http://127.0.0.1:3901).
16
+ * Required — never read from the environment (spec/modes.md "The SDK reads
17
+ * no environment variables. Credentials arrive as explicit options.").
18
+ */
19
+ apiUrl?: string;
20
+ /**
21
+ * Fireweave project/runtime key (project-api-key_…).
22
+ * Required — never read from the environment (spec/modes.md "The SDK reads
23
+ * no environment variables. Credentials arrive as explicit options.").
24
+ */
25
+ apiKey?: string;
26
+ /**
27
+ * SSRF allowlist override. Default: hostname of `apiUrl` + loopback.
28
+ * Pass `['*']` to opt out of host pinning.
29
+ */
30
+ allowedHosts?: readonly string[];
31
+ /** Per-request timeout for evaluate/capture (default 3000 ms). */
32
+ requestTimeoutMs?: number;
33
+ /** Deadline for flush during shutdown (default 10 000 ms). */
34
+ shutdownTimeoutMs?: number;
35
+ /** Injected fetch (tests). */
36
+ fetch?: FetchLike;
37
+ }
38
+ export declare class FireweaveRemoteAdapter implements BackendAdapter {
39
+ readonly name: "fireweave";
40
+ private readonly options;
41
+ private apiUrl;
42
+ private apiKey;
43
+ private closed;
44
+ private ready;
45
+ private readonly pending;
46
+ private readonly fetchImpl;
47
+ constructor(options?: FireweaveRemoteAdapterOptions);
48
+ initialize(_signal?: AbortSignal): Promise<void>;
49
+ resolve(controlPointKey: string, context: CanonicalContext, options?: ResolveOptions): Promise<AdapterResolution>;
50
+ /**
51
+ * Register a user or device so flag rules can target its DURABLE properties.
52
+ *
53
+ * Call once per login / device provisioning, then send the same
54
+ * `targetingKey` on evaluate. Per-request `attributes` still override the
55
+ * stored properties for a single evaluation — the two identity paths compose
56
+ * (see spec/remote-protocol.md § Two identity paths).
57
+ *
58
+ * Never throws for transport failures: registration sits in login paths, and
59
+ * an analytics call must not break sign-in. Retried ONCE when the error
60
+ * taxonomy marks the failure retryable (network / timeout / backend); a
61
+ * rejected payload or bad key is not retried, since it would be rejected
62
+ * identically. The result object reports what happened for callers that
63
+ * want to log it.
64
+ */
65
+ registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
66
+ recordExposure(exposure: Exposure): void;
67
+ recordSignal(signal: Signal): void;
68
+ flush(): Promise<void>;
69
+ shutdown(): Promise<void>;
70
+ features(): AdapterRuntimeFeatures;
71
+ private toResolution;
72
+ private requestJson;
73
+ }
74
+ export {};