@nexart/signals 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## v0.1.0
4
+
5
+ > Initial release. Fully independent of `@nexart/ai-execution` — no shared dependencies.
6
+
7
+ - **`createSignal(input)`**: normalizes a `CreateSignalInput` into a fully-populated `NexArtSignal`. Only `type` and `source` are required. All other fields default safely (`step: 0`, `actor: "unknown"`, `status: "ok"`, `payload: {}`, `timestamp: now`). No undefined values in output.
8
+
9
+ - **`createSignalCollector(options?)`**: ordered signal buffer. Auto-assigns step values in insertion order when signals omit `step`. Supports `defaultSource` and `defaultActor` collector-level options. `export()` sorts by step and is non-destructive. `size` property for quick count.
10
+
11
+ - **Types**: `NexArtSignal`, `CreateSignalInput`, `CollectorOptions`, `SignalCollection`, `SignalCollector` — all exported from package root.
12
+
13
+ - **Build**: dual ESM/CJS with tsup, full TypeScript declaration files.
14
+
15
+ - **Tests**: 45 tests across 9 describe blocks covering normalization, defaults, insertion order, step sorting, collector options, non-destructive export, size, package exports, and determinism.
16
+
17
+ - **Example**: `examples/basic.ts` — standalone signal, collected pipeline run, explicit step sorting, CER binding preview.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # @nexart/signals v0.1.0
2
+
3
+ Minimal, protocol-agnostic signal capture SDK.
4
+
5
+ Captures and normalizes structured upstream signals so they can be bound into **Certified Execution Records (CERs)** as optional context evidence.
6
+
7
+ - Does **not** define governance semantics or enforce policy
8
+ - Does **not** interpret the meaning of signals
9
+ - Validates structure only and provides normalization helpers
10
+ - Fully optional and independent of `@nexart/ai-execution`
11
+
12
+ ---
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @nexart/signals
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Quick start
23
+
24
+ ```ts
25
+ import { createSignal, createSignalCollector } from '@nexart/signals';
26
+
27
+ // ── Standalone signal ──────────────────────────────────────────────────────
28
+
29
+ const signal = createSignal({
30
+ type: 'approval',
31
+ source: 'github-actions',
32
+ actor: 'ci-bot',
33
+ status: 'ok',
34
+ payload: { pr: 42, approved_by: 'alice' },
35
+ });
36
+
37
+ // {
38
+ // type: 'approval',
39
+ // source: 'github-actions',
40
+ // step: 0,
41
+ // timestamp: '2026-...',
42
+ // actor: 'ci-bot',
43
+ // status: 'ok',
44
+ // payload: { pr: 42, approved_by: 'alice' }
45
+ // }
46
+
47
+
48
+ // ── Collected signals ──────────────────────────────────────────────────────
49
+
50
+ const collector = createSignalCollector({ defaultSource: 'my-pipeline' });
51
+
52
+ collector.add({ type: 'fetch', source: 'my-pipeline', payload: { url: '...' } });
53
+ collector.add({ type: 'transform', source: 'my-pipeline', actor: 'etl-bot' });
54
+ collector.add({ type: 'store', source: 'my-pipeline', status: 'ok' });
55
+
56
+ const collection = collector.export();
57
+ // {
58
+ // signals: [
59
+ // { type: 'fetch', step: 0, ... },
60
+ // { type: 'transform', step: 1, ... },
61
+ // { type: 'store', step: 2, ... },
62
+ // ],
63
+ // count: 3,
64
+ // exportedAt: '2026-...'
65
+ // }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Signal shape
71
+
72
+ Every `NexArtSignal` has exactly these fields — all always present, no undefined values:
73
+
74
+ | Field | Type | Default | Description |
75
+ |---|---|---|---|
76
+ | `type` | `string` | required | Signal category — free-form (e.g. `"approval"`, `"deploy"`, `"audit"`) |
77
+ | `source` | `string` | required | Upstream system or protocol — free-form (e.g. `"github-actions"`, `"linear"`) |
78
+ | `step` | `number` | `0` / auto | Position in sequence. Auto-assigned in insertion order by the collector |
79
+ | `timestamp` | `string` | current time | ISO 8601 |
80
+ | `actor` | `string` | `"unknown"` | Who produced this signal — free-form |
81
+ | `status` | `string` | `"ok"` | Outcome — free-form (e.g. `"ok"`, `"error"`, `"pending"`, `"skipped"`) |
82
+ | `payload` | `Record<string, unknown>` | `{}` | Opaque upstream data — NexArt does not interpret this |
83
+
84
+ ---
85
+
86
+ ## API
87
+
88
+ ### `createSignal(input)`
89
+
90
+ Creates a single normalized signal. `type` and `source` are required. All other fields are optional with safe defaults.
91
+
92
+ ```ts
93
+ const signal = createSignal({
94
+ type: 'review',
95
+ source: 'linear',
96
+ step: 2,
97
+ actor: 'alice',
98
+ status: 'ok',
99
+ payload: { issue: 'NX-123', verdict: 'approved' },
100
+ });
101
+ ```
102
+
103
+ ### `createSignalCollector(options?)`
104
+
105
+ Creates an ordered signal buffer. Returns a `SignalCollector` with three members:
106
+
107
+ #### `collector.add(input)`
108
+
109
+ Adds a signal. Returns the normalized `NexArtSignal` that was stored.
110
+
111
+ - If `step` is omitted, it is auto-assigned using the insertion index (0, 1, 2, …)
112
+ - If `step` is explicitly provided, that value is used and the signal is sorted correctly on export
113
+
114
+ #### `collector.export()`
115
+
116
+ Returns a `SignalCollection`:
117
+
118
+ ```ts
119
+ interface SignalCollection {
120
+ signals: NexArtSignal[]; // sorted by step (ascending)
121
+ count: number;
122
+ exportedAt: string; // ISO 8601, time of export() call
123
+ }
124
+ ```
125
+
126
+ Non-destructive — calling `export()` multiple times is safe. The internal buffer is not cleared.
127
+
128
+ #### `collector.size`
129
+
130
+ Returns the current number of collected signals.
131
+
132
+ #### Collector options
133
+
134
+ ```ts
135
+ interface CollectorOptions {
136
+ defaultSource?: string; // applied when signal source is empty/omitted
137
+ defaultActor?: string; // applied when signal actor is omitted
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Step ordering
144
+
145
+ Signals without an explicit `step` get steps assigned in insertion order:
146
+
147
+ ```ts
148
+ collector.add({ type: 'a', source: 's' }); // step = 0
149
+ collector.add({ type: 'b', source: 's' }); // step = 1
150
+ collector.add({ type: 'c', source: 's' }); // step = 2
151
+ ```
152
+
153
+ Signals with an explicit `step` are sorted by that value on export, regardless of insertion order:
154
+
155
+ ```ts
156
+ collector.add({ type: 'deploy', source: 's', step: 10 });
157
+ collector.add({ type: 'build', source: 's', step: 0 });
158
+ collector.add({ type: 'test', source: 's', step: 5 });
159
+
160
+ collector.export().signals.map(s => s.type);
161
+ // ['build', 'test', 'deploy']
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Determinism
167
+
168
+ Pin `timestamp` and `step` for deterministic output that can be hashed:
169
+
170
+ ```ts
171
+ const signal = createSignal({
172
+ type: 'approval',
173
+ source: 'ci',
174
+ step: 0,
175
+ timestamp: '2026-03-17T00:00:00.000Z', // pinned
176
+ actor: 'bot',
177
+ payload: { pr: 42 },
178
+ });
179
+ // same input → identical object every time
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Integration with @nexart/ai-execution (v0.10.0+)
185
+
186
+ `NexArtSignal[]` is structurally identical to `CerContextSignal[]` in `@nexart/ai-execution` — no casting or conversion needed. Pass `collection.signals` directly to any certify call and it will be sealed into the `certificateHash` alongside the execution record.
187
+
188
+ ```ts
189
+ import { createSignalCollector } from '@nexart/signals';
190
+ import { certifyDecision, certifyLangChainRun, verifyCer } from '@nexart/ai-execution';
191
+
192
+ const collector = createSignalCollector({ defaultSource: 'github-actions' });
193
+ collector.add({ type: 'approval', actor: 'alice', status: 'ok', payload: { pr: 42 } });
194
+ collector.add({ type: 'deploy', actor: 'ci-bot', status: 'ok', payload: { env: 'prod' } });
195
+
196
+ const { signals } = collector.export();
197
+
198
+ // ── certifyDecision ──────────────────────────────────────────
199
+ const bundle = certifyDecision({
200
+ provider: 'openai',
201
+ model: 'gpt-4o-mini',
202
+ prompt: 'Summarise.',
203
+ input: userQuery,
204
+ output: llmResponse,
205
+ parameters: { temperature: 0, maxTokens: 512, topP: null, seed: null },
206
+ signals, // ← sealed into certificateHash; omit = identical hash as before
207
+ });
208
+
209
+ verifyCer(bundle).ok; // true
210
+ bundle.context?.signals.length; // 2
211
+
212
+ // ── certifyLangChainRun ──────────────────────────────────────
213
+ const { bundle: lcBundle } = certifyLangChainRun({
214
+ provider: 'openai',
215
+ model: 'gpt-4o-mini',
216
+ input: { messages: [{ role: 'user', content: 'Summarise.' }] },
217
+ output: { text: 'Summary...' },
218
+ signals, // ← same field, same semantics
219
+ });
220
+ ```
221
+
222
+ Signals are evidence-only — they do not affect execution behavior or parameters. See `@nexart/ai-execution` README for the full `CerContextSignal` shape and tamper-detection guarantees.
223
+
224
+ ---
225
+
226
+ ## Version history
227
+
228
+ | Version | Description |
229
+ |---|---|
230
+ | v0.1.0 | Initial release: `createSignal`, `createSignalCollector`, `NexArtSignal`, `SignalCollection`, `CollectorOptions`, `SignalCollector` types |
package/dist/index.cjs ADDED
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ SIGNALS_VERSION: () => SIGNALS_VERSION,
24
+ createSignal: () => createSignal,
25
+ createSignalCollector: () => createSignalCollector
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/create.ts
30
+ function createSignal(input) {
31
+ return normalize(input, input.step ?? 0);
32
+ }
33
+ function normalize(input, resolvedStep) {
34
+ return {
35
+ type: input.type,
36
+ source: input.source,
37
+ step: resolvedStep,
38
+ timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
39
+ actor: input.actor ?? "unknown",
40
+ status: input.status ?? "ok",
41
+ payload: input.payload ?? {}
42
+ };
43
+ }
44
+
45
+ // src/collector.ts
46
+ function createSignalCollector(options) {
47
+ const buffer = [];
48
+ let insertionIndex = 0;
49
+ return {
50
+ add(input) {
51
+ const resolvedStep = input.step ?? insertionIndex;
52
+ const resolved = {
53
+ ...input,
54
+ source: input.source !== void 0 && input.source !== "" ? input.source : options?.defaultSource ?? input.source ?? "",
55
+ actor: input.actor !== void 0 && input.actor !== "" ? input.actor : options?.defaultActor ?? input.actor
56
+ };
57
+ const signal = normalize(resolved, resolvedStep);
58
+ buffer.push(signal);
59
+ insertionIndex++;
60
+ return signal;
61
+ },
62
+ export() {
63
+ const sorted = [...buffer].sort((a, b) => a.step - b.step);
64
+ return {
65
+ signals: sorted,
66
+ count: sorted.length,
67
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
68
+ };
69
+ },
70
+ get size() {
71
+ return buffer.length;
72
+ }
73
+ };
74
+ }
75
+
76
+ // src/index.ts
77
+ var SIGNALS_VERSION = "0.1.0";
78
+ // Annotate the CommonJS export names for ESM import in node:
79
+ 0 && (module.exports = {
80
+ SIGNALS_VERSION,
81
+ createSignal,
82
+ createSignalCollector
83
+ });
84
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/create.ts","../src/collector.ts"],"sourcesContent":["/**\n * @nexart/signals v0.1.0\n *\n * Minimal, protocol-agnostic signal capture SDK.\n *\n * Captures and normalizes structured upstream signals so they can be bound\n * into Certified Execution Records (CERs) as optional context evidence.\n *\n * Does NOT define governance semantics, enforce policy, or interpret meaning.\n * Only validates structure and provides normalization helpers.\n *\n * @example\n * ```ts\n * import { createSignal, createSignalCollector } from '@nexart/signals';\n *\n * // Standalone signal\n * const signal = createSignal({\n * type: 'approval',\n * source: 'github-actions',\n * actor: 'ci-bot',\n * payload: { pr: 42 },\n * });\n *\n * // Collected signals\n * const collector = createSignalCollector();\n * collector.add({ type: 'fetch', source: 'pipeline', payload: { url: '...' } });\n * collector.add({ type: 'store', source: 'pipeline', actor: 'etl-bot' });\n * const collection = collector.export();\n * ```\n */\n\nexport { createSignal } from './create.js';\nexport { createSignalCollector } from './collector.js';\n\nexport type {\n NexArtSignal,\n CreateSignalInput,\n CollectorOptions,\n SignalCollection,\n SignalCollector,\n} from './types.js';\n\nexport const SIGNALS_VERSION = '0.1.0' as const;\n","/**\n * @nexart/signals — createSignal()\n *\n * Normalizes a CreateSignalInput into a fully-populated NexArtSignal.\n * Every field is always present — no undefined values in the output.\n */\n\nimport type { CreateSignalInput, NexArtSignal } from './types.js';\n\n/**\n * Create a single normalized NexArtSignal from a CreateSignalInput.\n *\n * - type and source are required.\n * - step defaults to 0 if omitted (use a collector for auto-incrementing step).\n * - timestamp defaults to the current time (ISO 8601).\n * - actor defaults to \"unknown\".\n * - status defaults to \"ok\".\n * - payload defaults to {}.\n *\n * @example\n * ```ts\n * const signal = createSignal({\n * type: 'approval',\n * source: 'github-actions',\n * actor: 'ci-bot',\n * status: 'ok',\n * payload: { pr: 42, approved_by: 'alice' },\n * });\n * ```\n */\nexport function createSignal(input: CreateSignalInput): NexArtSignal {\n return normalize(input, input.step ?? 0);\n}\n\n/**\n * Internal normalization helper — shared by createSignal() and the collector.\n * Applies all field defaults deterministically.\n *\n * @internal\n */\nexport function normalize(input: CreateSignalInput, resolvedStep: number): NexArtSignal {\n return {\n type: input.type,\n source: input.source,\n step: resolvedStep,\n timestamp: input.timestamp ?? new Date().toISOString(),\n actor: input.actor ?? 'unknown',\n status: input.status ?? 'ok',\n payload: input.payload ?? {},\n };\n}\n","/**\n * @nexart/signals — createSignalCollector()\n *\n * A lightweight, ordered buffer for collecting signals from multiple\n * upstream sources before exporting them as a single SignalCollection.\n *\n * Auto-assigns step values in insertion order when signals do not\n * carry an explicit step. Signals with explicit steps are sorted\n * by step on export.\n */\n\nimport { normalize } from './create.js';\nimport type {\n CollectorOptions,\n CreateSignalInput,\n NexArtSignal,\n SignalCollection,\n SignalCollector,\n} from './types.js';\n\n/**\n * Create a signal collector.\n *\n * The collector maintains insertion order and auto-assigns step values.\n * When a signal provides an explicit step, that value is used as-is.\n * On export(), all signals are sorted by step (ascending).\n *\n * @example\n * ```ts\n * const collector = createSignalCollector({ defaultSource: 'my-pipeline' });\n *\n * collector.add({ type: 'fetch', source: 'my-pipeline', payload: { url: '...' } });\n * collector.add({ type: 'transform', source: 'my-pipeline', status: 'ok' });\n * collector.add({ type: 'store', source: 'my-pipeline', actor: 'etl-bot' });\n *\n * const collection = collector.export();\n * // collection.signals[0].step === 0\n * // collection.signals[1].step === 1\n * // collection.signals[2].step === 2\n * ```\n */\nexport function createSignalCollector(options?: CollectorOptions): SignalCollector {\n const buffer: NexArtSignal[] = [];\n let insertionIndex = 0;\n\n return {\n add(input: CreateSignalInput): NexArtSignal {\n const resolvedStep = input.step ?? insertionIndex;\n\n const resolved: CreateSignalInput = {\n ...input,\n source: input.source !== undefined && input.source !== ''\n ? input.source\n : (options?.defaultSource ?? input.source ?? ''),\n actor: input.actor !== undefined && input.actor !== ''\n ? input.actor\n : (options?.defaultActor ?? input.actor),\n };\n\n const signal = normalize(resolved, resolvedStep);\n buffer.push(signal);\n insertionIndex++;\n return signal;\n },\n\n export(): SignalCollection {\n const sorted = [...buffer].sort((a, b) => a.step - b.step);\n return {\n signals: sorted,\n count: sorted.length,\n exportedAt: new Date().toISOString(),\n };\n },\n\n get size(): number {\n return buffer.length;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC8BO,SAAS,aAAa,OAAwC;AACnE,SAAO,UAAU,OAAO,MAAM,QAAQ,CAAC;AACzC;AAQO,SAAS,UAAU,OAA0B,cAAoC;AACtF,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,IACd,MAAM;AAAA,IACN,WAAW,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrD,OAAO,MAAM,SAAS;AAAA,IACtB,QAAQ,MAAM,UAAU;AAAA,IACxB,SAAS,MAAM,WAAW,CAAC;AAAA,EAC7B;AACF;;;ACTO,SAAS,sBAAsB,SAA6C;AACjF,QAAM,SAAyB,CAAC;AAChC,MAAI,iBAAiB;AAErB,SAAO;AAAA,IACL,IAAI,OAAwC;AAC1C,YAAM,eAAe,MAAM,QAAQ;AAEnC,YAAM,WAA8B;AAAA,QAClC,GAAG;AAAA,QACH,QAAQ,MAAM,WAAW,UAAa,MAAM,WAAW,KACnD,MAAM,SACL,SAAS,iBAAiB,MAAM,UAAU;AAAA,QAC/C,OAAO,MAAM,UAAU,UAAa,MAAM,UAAU,KAChD,MAAM,QACL,SAAS,gBAAgB,MAAM;AAAA,MACtC;AAEA,YAAM,SAAS,UAAU,UAAU,YAAY;AAC/C,aAAO,KAAK,MAAM;AAClB;AACA,aAAO;AAAA,IACT;AAAA,IAEA,SAA2B;AACzB,YAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AACzD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,QACd,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AAAA,IACF;AAAA,IAEA,IAAI,OAAe;AACjB,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AACF;;;AFpCO,IAAM,kBAAkB;","names":[]}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @nexart/signals — Core signal types
3
+ *
4
+ * All types are intentionally protocol-agnostic.
5
+ * The SDK validates structure only — it does not interpret meaning.
6
+ */
7
+ /**
8
+ * A normalized upstream signal captured for optional inclusion in a CER.
9
+ *
10
+ * Fields are normalized at creation time — all fields are always present
11
+ * (no undefined values) to make hashing and downstream consumption reliable.
12
+ */
13
+ interface NexArtSignal {
14
+ /** Signal category. Free-form — examples: "approval", "review", "deploy", "audit". */
15
+ type: string;
16
+ /** Upstream system or protocol that produced this signal. Free-form. */
17
+ source: string;
18
+ /**
19
+ * Position of this signal in a sequence.
20
+ * Auto-assigned (0-based insertion order) when not explicitly provided.
21
+ */
22
+ step: number;
23
+ /** ISO 8601 timestamp. Defaults to the time createSignal() was called. */
24
+ timestamp: string;
25
+ /** Actor that produced this signal. Free-form — defaults to "unknown". */
26
+ actor: string;
27
+ /**
28
+ * Outcome or state of the signal. Free-form — defaults to "ok".
29
+ * Examples: "ok", "error", "pending", "skipped".
30
+ */
31
+ status: string;
32
+ /**
33
+ * Opaque payload from the upstream system.
34
+ * NexArt does not interpret, validate, or modify this field.
35
+ * Defaults to {} if omitted.
36
+ */
37
+ payload: Record<string, unknown>;
38
+ }
39
+ /**
40
+ * Input to createSignal(). Only type and source are required.
41
+ * All other fields are optional and normalized to safe defaults.
42
+ */
43
+ interface CreateSignalInput {
44
+ /** Signal category. Required. */
45
+ type: string;
46
+ /** Upstream system or protocol. Required. */
47
+ source: string;
48
+ /**
49
+ * Explicit step index. When omitted on a collector, auto-assigned
50
+ * in insertion order. When omitted on standalone createSignal(), defaults to 0.
51
+ */
52
+ step?: number;
53
+ /** ISO 8601 timestamp. Defaults to current time if omitted. */
54
+ timestamp?: string;
55
+ /** Actor that produced this signal. Defaults to "unknown". */
56
+ actor?: string;
57
+ /**
58
+ * Outcome or state. Defaults to "ok".
59
+ * Examples: "ok", "error", "pending", "skipped".
60
+ */
61
+ status?: string;
62
+ /**
63
+ * Opaque payload. NexArt does not interpret this.
64
+ * Defaults to {} if omitted.
65
+ */
66
+ payload?: Record<string, unknown>;
67
+ }
68
+ /** Options for createSignalCollector(). */
69
+ interface CollectorOptions {
70
+ /**
71
+ * Default source applied to every signal added via collector.add()
72
+ * when the signal does not supply its own source.
73
+ */
74
+ defaultSource?: string;
75
+ /**
76
+ * Default actor applied to every signal added via collector.add()
77
+ * when the signal does not supply its own actor.
78
+ */
79
+ defaultActor?: string;
80
+ }
81
+ /**
82
+ * Exported signal collection produced by collector.export().
83
+ * Stable, deterministic, and ready to be hashed or bound into a CER.
84
+ */
85
+ interface SignalCollection {
86
+ /** Signals sorted by step (ascending). */
87
+ signals: NexArtSignal[];
88
+ /** Total number of signals in this collection. */
89
+ count: number;
90
+ /** ISO 8601 timestamp of when export() was called. */
91
+ exportedAt: string;
92
+ }
93
+ /** A signal collector returned by createSignalCollector(). */
94
+ interface SignalCollector {
95
+ /**
96
+ * Add a signal to the collector.
97
+ * Returns the normalized NexArtSignal that was stored.
98
+ */
99
+ add(input: CreateSignalInput): NexArtSignal;
100
+ /**
101
+ * Export all collected signals as a stable SignalCollection.
102
+ * Signals are sorted by step (ascending).
103
+ * Does not clear the internal buffer — calling export() twice is safe.
104
+ */
105
+ export(): SignalCollection;
106
+ /**
107
+ * Return the current number of signals in the collector.
108
+ */
109
+ readonly size: number;
110
+ }
111
+
112
+ /**
113
+ * @nexart/signals — createSignal()
114
+ *
115
+ * Normalizes a CreateSignalInput into a fully-populated NexArtSignal.
116
+ * Every field is always present — no undefined values in the output.
117
+ */
118
+
119
+ /**
120
+ * Create a single normalized NexArtSignal from a CreateSignalInput.
121
+ *
122
+ * - type and source are required.
123
+ * - step defaults to 0 if omitted (use a collector for auto-incrementing step).
124
+ * - timestamp defaults to the current time (ISO 8601).
125
+ * - actor defaults to "unknown".
126
+ * - status defaults to "ok".
127
+ * - payload defaults to {}.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const signal = createSignal({
132
+ * type: 'approval',
133
+ * source: 'github-actions',
134
+ * actor: 'ci-bot',
135
+ * status: 'ok',
136
+ * payload: { pr: 42, approved_by: 'alice' },
137
+ * });
138
+ * ```
139
+ */
140
+ declare function createSignal(input: CreateSignalInput): NexArtSignal;
141
+
142
+ /**
143
+ * @nexart/signals — createSignalCollector()
144
+ *
145
+ * A lightweight, ordered buffer for collecting signals from multiple
146
+ * upstream sources before exporting them as a single SignalCollection.
147
+ *
148
+ * Auto-assigns step values in insertion order when signals do not
149
+ * carry an explicit step. Signals with explicit steps are sorted
150
+ * by step on export.
151
+ */
152
+
153
+ /**
154
+ * Create a signal collector.
155
+ *
156
+ * The collector maintains insertion order and auto-assigns step values.
157
+ * When a signal provides an explicit step, that value is used as-is.
158
+ * On export(), all signals are sorted by step (ascending).
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const collector = createSignalCollector({ defaultSource: 'my-pipeline' });
163
+ *
164
+ * collector.add({ type: 'fetch', source: 'my-pipeline', payload: { url: '...' } });
165
+ * collector.add({ type: 'transform', source: 'my-pipeline', status: 'ok' });
166
+ * collector.add({ type: 'store', source: 'my-pipeline', actor: 'etl-bot' });
167
+ *
168
+ * const collection = collector.export();
169
+ * // collection.signals[0].step === 0
170
+ * // collection.signals[1].step === 1
171
+ * // collection.signals[2].step === 2
172
+ * ```
173
+ */
174
+ declare function createSignalCollector(options?: CollectorOptions): SignalCollector;
175
+
176
+ /**
177
+ * @nexart/signals v0.1.0
178
+ *
179
+ * Minimal, protocol-agnostic signal capture SDK.
180
+ *
181
+ * Captures and normalizes structured upstream signals so they can be bound
182
+ * into Certified Execution Records (CERs) as optional context evidence.
183
+ *
184
+ * Does NOT define governance semantics, enforce policy, or interpret meaning.
185
+ * Only validates structure and provides normalization helpers.
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * import { createSignal, createSignalCollector } from '@nexart/signals';
190
+ *
191
+ * // Standalone signal
192
+ * const signal = createSignal({
193
+ * type: 'approval',
194
+ * source: 'github-actions',
195
+ * actor: 'ci-bot',
196
+ * payload: { pr: 42 },
197
+ * });
198
+ *
199
+ * // Collected signals
200
+ * const collector = createSignalCollector();
201
+ * collector.add({ type: 'fetch', source: 'pipeline', payload: { url: '...' } });
202
+ * collector.add({ type: 'store', source: 'pipeline', actor: 'etl-bot' });
203
+ * const collection = collector.export();
204
+ * ```
205
+ */
206
+
207
+ declare const SIGNALS_VERSION: "0.1.0";
208
+
209
+ export { type CollectorOptions, type CreateSignalInput, type NexArtSignal, SIGNALS_VERSION, type SignalCollection, type SignalCollector, createSignal, createSignalCollector };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @nexart/signals — Core signal types
3
+ *
4
+ * All types are intentionally protocol-agnostic.
5
+ * The SDK validates structure only — it does not interpret meaning.
6
+ */
7
+ /**
8
+ * A normalized upstream signal captured for optional inclusion in a CER.
9
+ *
10
+ * Fields are normalized at creation time — all fields are always present
11
+ * (no undefined values) to make hashing and downstream consumption reliable.
12
+ */
13
+ interface NexArtSignal {
14
+ /** Signal category. Free-form — examples: "approval", "review", "deploy", "audit". */
15
+ type: string;
16
+ /** Upstream system or protocol that produced this signal. Free-form. */
17
+ source: string;
18
+ /**
19
+ * Position of this signal in a sequence.
20
+ * Auto-assigned (0-based insertion order) when not explicitly provided.
21
+ */
22
+ step: number;
23
+ /** ISO 8601 timestamp. Defaults to the time createSignal() was called. */
24
+ timestamp: string;
25
+ /** Actor that produced this signal. Free-form — defaults to "unknown". */
26
+ actor: string;
27
+ /**
28
+ * Outcome or state of the signal. Free-form — defaults to "ok".
29
+ * Examples: "ok", "error", "pending", "skipped".
30
+ */
31
+ status: string;
32
+ /**
33
+ * Opaque payload from the upstream system.
34
+ * NexArt does not interpret, validate, or modify this field.
35
+ * Defaults to {} if omitted.
36
+ */
37
+ payload: Record<string, unknown>;
38
+ }
39
+ /**
40
+ * Input to createSignal(). Only type and source are required.
41
+ * All other fields are optional and normalized to safe defaults.
42
+ */
43
+ interface CreateSignalInput {
44
+ /** Signal category. Required. */
45
+ type: string;
46
+ /** Upstream system or protocol. Required. */
47
+ source: string;
48
+ /**
49
+ * Explicit step index. When omitted on a collector, auto-assigned
50
+ * in insertion order. When omitted on standalone createSignal(), defaults to 0.
51
+ */
52
+ step?: number;
53
+ /** ISO 8601 timestamp. Defaults to current time if omitted. */
54
+ timestamp?: string;
55
+ /** Actor that produced this signal. Defaults to "unknown". */
56
+ actor?: string;
57
+ /**
58
+ * Outcome or state. Defaults to "ok".
59
+ * Examples: "ok", "error", "pending", "skipped".
60
+ */
61
+ status?: string;
62
+ /**
63
+ * Opaque payload. NexArt does not interpret this.
64
+ * Defaults to {} if omitted.
65
+ */
66
+ payload?: Record<string, unknown>;
67
+ }
68
+ /** Options for createSignalCollector(). */
69
+ interface CollectorOptions {
70
+ /**
71
+ * Default source applied to every signal added via collector.add()
72
+ * when the signal does not supply its own source.
73
+ */
74
+ defaultSource?: string;
75
+ /**
76
+ * Default actor applied to every signal added via collector.add()
77
+ * when the signal does not supply its own actor.
78
+ */
79
+ defaultActor?: string;
80
+ }
81
+ /**
82
+ * Exported signal collection produced by collector.export().
83
+ * Stable, deterministic, and ready to be hashed or bound into a CER.
84
+ */
85
+ interface SignalCollection {
86
+ /** Signals sorted by step (ascending). */
87
+ signals: NexArtSignal[];
88
+ /** Total number of signals in this collection. */
89
+ count: number;
90
+ /** ISO 8601 timestamp of when export() was called. */
91
+ exportedAt: string;
92
+ }
93
+ /** A signal collector returned by createSignalCollector(). */
94
+ interface SignalCollector {
95
+ /**
96
+ * Add a signal to the collector.
97
+ * Returns the normalized NexArtSignal that was stored.
98
+ */
99
+ add(input: CreateSignalInput): NexArtSignal;
100
+ /**
101
+ * Export all collected signals as a stable SignalCollection.
102
+ * Signals are sorted by step (ascending).
103
+ * Does not clear the internal buffer — calling export() twice is safe.
104
+ */
105
+ export(): SignalCollection;
106
+ /**
107
+ * Return the current number of signals in the collector.
108
+ */
109
+ readonly size: number;
110
+ }
111
+
112
+ /**
113
+ * @nexart/signals — createSignal()
114
+ *
115
+ * Normalizes a CreateSignalInput into a fully-populated NexArtSignal.
116
+ * Every field is always present — no undefined values in the output.
117
+ */
118
+
119
+ /**
120
+ * Create a single normalized NexArtSignal from a CreateSignalInput.
121
+ *
122
+ * - type and source are required.
123
+ * - step defaults to 0 if omitted (use a collector for auto-incrementing step).
124
+ * - timestamp defaults to the current time (ISO 8601).
125
+ * - actor defaults to "unknown".
126
+ * - status defaults to "ok".
127
+ * - payload defaults to {}.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const signal = createSignal({
132
+ * type: 'approval',
133
+ * source: 'github-actions',
134
+ * actor: 'ci-bot',
135
+ * status: 'ok',
136
+ * payload: { pr: 42, approved_by: 'alice' },
137
+ * });
138
+ * ```
139
+ */
140
+ declare function createSignal(input: CreateSignalInput): NexArtSignal;
141
+
142
+ /**
143
+ * @nexart/signals — createSignalCollector()
144
+ *
145
+ * A lightweight, ordered buffer for collecting signals from multiple
146
+ * upstream sources before exporting them as a single SignalCollection.
147
+ *
148
+ * Auto-assigns step values in insertion order when signals do not
149
+ * carry an explicit step. Signals with explicit steps are sorted
150
+ * by step on export.
151
+ */
152
+
153
+ /**
154
+ * Create a signal collector.
155
+ *
156
+ * The collector maintains insertion order and auto-assigns step values.
157
+ * When a signal provides an explicit step, that value is used as-is.
158
+ * On export(), all signals are sorted by step (ascending).
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const collector = createSignalCollector({ defaultSource: 'my-pipeline' });
163
+ *
164
+ * collector.add({ type: 'fetch', source: 'my-pipeline', payload: { url: '...' } });
165
+ * collector.add({ type: 'transform', source: 'my-pipeline', status: 'ok' });
166
+ * collector.add({ type: 'store', source: 'my-pipeline', actor: 'etl-bot' });
167
+ *
168
+ * const collection = collector.export();
169
+ * // collection.signals[0].step === 0
170
+ * // collection.signals[1].step === 1
171
+ * // collection.signals[2].step === 2
172
+ * ```
173
+ */
174
+ declare function createSignalCollector(options?: CollectorOptions): SignalCollector;
175
+
176
+ /**
177
+ * @nexart/signals v0.1.0
178
+ *
179
+ * Minimal, protocol-agnostic signal capture SDK.
180
+ *
181
+ * Captures and normalizes structured upstream signals so they can be bound
182
+ * into Certified Execution Records (CERs) as optional context evidence.
183
+ *
184
+ * Does NOT define governance semantics, enforce policy, or interpret meaning.
185
+ * Only validates structure and provides normalization helpers.
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * import { createSignal, createSignalCollector } from '@nexart/signals';
190
+ *
191
+ * // Standalone signal
192
+ * const signal = createSignal({
193
+ * type: 'approval',
194
+ * source: 'github-actions',
195
+ * actor: 'ci-bot',
196
+ * payload: { pr: 42 },
197
+ * });
198
+ *
199
+ * // Collected signals
200
+ * const collector = createSignalCollector();
201
+ * collector.add({ type: 'fetch', source: 'pipeline', payload: { url: '...' } });
202
+ * collector.add({ type: 'store', source: 'pipeline', actor: 'etl-bot' });
203
+ * const collection = collector.export();
204
+ * ```
205
+ */
206
+
207
+ declare const SIGNALS_VERSION: "0.1.0";
208
+
209
+ export { type CollectorOptions, type CreateSignalInput, type NexArtSignal, SIGNALS_VERSION, type SignalCollection, type SignalCollector, createSignal, createSignalCollector };
package/dist/index.mjs ADDED
@@ -0,0 +1,55 @@
1
+ // src/create.ts
2
+ function createSignal(input) {
3
+ return normalize(input, input.step ?? 0);
4
+ }
5
+ function normalize(input, resolvedStep) {
6
+ return {
7
+ type: input.type,
8
+ source: input.source,
9
+ step: resolvedStep,
10
+ timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
11
+ actor: input.actor ?? "unknown",
12
+ status: input.status ?? "ok",
13
+ payload: input.payload ?? {}
14
+ };
15
+ }
16
+
17
+ // src/collector.ts
18
+ function createSignalCollector(options) {
19
+ const buffer = [];
20
+ let insertionIndex = 0;
21
+ return {
22
+ add(input) {
23
+ const resolvedStep = input.step ?? insertionIndex;
24
+ const resolved = {
25
+ ...input,
26
+ source: input.source !== void 0 && input.source !== "" ? input.source : options?.defaultSource ?? input.source ?? "",
27
+ actor: input.actor !== void 0 && input.actor !== "" ? input.actor : options?.defaultActor ?? input.actor
28
+ };
29
+ const signal = normalize(resolved, resolvedStep);
30
+ buffer.push(signal);
31
+ insertionIndex++;
32
+ return signal;
33
+ },
34
+ export() {
35
+ const sorted = [...buffer].sort((a, b) => a.step - b.step);
36
+ return {
37
+ signals: sorted,
38
+ count: sorted.length,
39
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
40
+ };
41
+ },
42
+ get size() {
43
+ return buffer.length;
44
+ }
45
+ };
46
+ }
47
+
48
+ // src/index.ts
49
+ var SIGNALS_VERSION = "0.1.0";
50
+ export {
51
+ SIGNALS_VERSION,
52
+ createSignal,
53
+ createSignalCollector
54
+ };
55
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/create.ts","../src/collector.ts","../src/index.ts"],"sourcesContent":["/**\n * @nexart/signals — createSignal()\n *\n * Normalizes a CreateSignalInput into a fully-populated NexArtSignal.\n * Every field is always present — no undefined values in the output.\n */\n\nimport type { CreateSignalInput, NexArtSignal } from './types.js';\n\n/**\n * Create a single normalized NexArtSignal from a CreateSignalInput.\n *\n * - type and source are required.\n * - step defaults to 0 if omitted (use a collector for auto-incrementing step).\n * - timestamp defaults to the current time (ISO 8601).\n * - actor defaults to \"unknown\".\n * - status defaults to \"ok\".\n * - payload defaults to {}.\n *\n * @example\n * ```ts\n * const signal = createSignal({\n * type: 'approval',\n * source: 'github-actions',\n * actor: 'ci-bot',\n * status: 'ok',\n * payload: { pr: 42, approved_by: 'alice' },\n * });\n * ```\n */\nexport function createSignal(input: CreateSignalInput): NexArtSignal {\n return normalize(input, input.step ?? 0);\n}\n\n/**\n * Internal normalization helper — shared by createSignal() and the collector.\n * Applies all field defaults deterministically.\n *\n * @internal\n */\nexport function normalize(input: CreateSignalInput, resolvedStep: number): NexArtSignal {\n return {\n type: input.type,\n source: input.source,\n step: resolvedStep,\n timestamp: input.timestamp ?? new Date().toISOString(),\n actor: input.actor ?? 'unknown',\n status: input.status ?? 'ok',\n payload: input.payload ?? {},\n };\n}\n","/**\n * @nexart/signals — createSignalCollector()\n *\n * A lightweight, ordered buffer for collecting signals from multiple\n * upstream sources before exporting them as a single SignalCollection.\n *\n * Auto-assigns step values in insertion order when signals do not\n * carry an explicit step. Signals with explicit steps are sorted\n * by step on export.\n */\n\nimport { normalize } from './create.js';\nimport type {\n CollectorOptions,\n CreateSignalInput,\n NexArtSignal,\n SignalCollection,\n SignalCollector,\n} from './types.js';\n\n/**\n * Create a signal collector.\n *\n * The collector maintains insertion order and auto-assigns step values.\n * When a signal provides an explicit step, that value is used as-is.\n * On export(), all signals are sorted by step (ascending).\n *\n * @example\n * ```ts\n * const collector = createSignalCollector({ defaultSource: 'my-pipeline' });\n *\n * collector.add({ type: 'fetch', source: 'my-pipeline', payload: { url: '...' } });\n * collector.add({ type: 'transform', source: 'my-pipeline', status: 'ok' });\n * collector.add({ type: 'store', source: 'my-pipeline', actor: 'etl-bot' });\n *\n * const collection = collector.export();\n * // collection.signals[0].step === 0\n * // collection.signals[1].step === 1\n * // collection.signals[2].step === 2\n * ```\n */\nexport function createSignalCollector(options?: CollectorOptions): SignalCollector {\n const buffer: NexArtSignal[] = [];\n let insertionIndex = 0;\n\n return {\n add(input: CreateSignalInput): NexArtSignal {\n const resolvedStep = input.step ?? insertionIndex;\n\n const resolved: CreateSignalInput = {\n ...input,\n source: input.source !== undefined && input.source !== ''\n ? input.source\n : (options?.defaultSource ?? input.source ?? ''),\n actor: input.actor !== undefined && input.actor !== ''\n ? input.actor\n : (options?.defaultActor ?? input.actor),\n };\n\n const signal = normalize(resolved, resolvedStep);\n buffer.push(signal);\n insertionIndex++;\n return signal;\n },\n\n export(): SignalCollection {\n const sorted = [...buffer].sort((a, b) => a.step - b.step);\n return {\n signals: sorted,\n count: sorted.length,\n exportedAt: new Date().toISOString(),\n };\n },\n\n get size(): number {\n return buffer.length;\n },\n };\n}\n","/**\n * @nexart/signals v0.1.0\n *\n * Minimal, protocol-agnostic signal capture SDK.\n *\n * Captures and normalizes structured upstream signals so they can be bound\n * into Certified Execution Records (CERs) as optional context evidence.\n *\n * Does NOT define governance semantics, enforce policy, or interpret meaning.\n * Only validates structure and provides normalization helpers.\n *\n * @example\n * ```ts\n * import { createSignal, createSignalCollector } from '@nexart/signals';\n *\n * // Standalone signal\n * const signal = createSignal({\n * type: 'approval',\n * source: 'github-actions',\n * actor: 'ci-bot',\n * payload: { pr: 42 },\n * });\n *\n * // Collected signals\n * const collector = createSignalCollector();\n * collector.add({ type: 'fetch', source: 'pipeline', payload: { url: '...' } });\n * collector.add({ type: 'store', source: 'pipeline', actor: 'etl-bot' });\n * const collection = collector.export();\n * ```\n */\n\nexport { createSignal } from './create.js';\nexport { createSignalCollector } from './collector.js';\n\nexport type {\n NexArtSignal,\n CreateSignalInput,\n CollectorOptions,\n SignalCollection,\n SignalCollector,\n} from './types.js';\n\nexport const SIGNALS_VERSION = '0.1.0' as const;\n"],"mappings":";AA8BO,SAAS,aAAa,OAAwC;AACnE,SAAO,UAAU,OAAO,MAAM,QAAQ,CAAC;AACzC;AAQO,SAAS,UAAU,OAA0B,cAAoC;AACtF,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,IACd,MAAM;AAAA,IACN,WAAW,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrD,OAAO,MAAM,SAAS;AAAA,IACtB,QAAQ,MAAM,UAAU;AAAA,IACxB,SAAS,MAAM,WAAW,CAAC;AAAA,EAC7B;AACF;;;ACTO,SAAS,sBAAsB,SAA6C;AACjF,QAAM,SAAyB,CAAC;AAChC,MAAI,iBAAiB;AAErB,SAAO;AAAA,IACL,IAAI,OAAwC;AAC1C,YAAM,eAAe,MAAM,QAAQ;AAEnC,YAAM,WAA8B;AAAA,QAClC,GAAG;AAAA,QACH,QAAQ,MAAM,WAAW,UAAa,MAAM,WAAW,KACnD,MAAM,SACL,SAAS,iBAAiB,MAAM,UAAU;AAAA,QAC/C,OAAO,MAAM,UAAU,UAAa,MAAM,UAAU,KAChD,MAAM,QACL,SAAS,gBAAgB,MAAM;AAAA,MACtC;AAEA,YAAM,SAAS,UAAU,UAAU,YAAY;AAC/C,aAAO,KAAK,MAAM;AAClB;AACA,aAAO;AAAA,IACT;AAAA,IAEA,SAA2B;AACzB,YAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AACzD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,QACd,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AAAA,IACF;AAAA,IAEA,IAAI,OAAe;AACjB,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AACF;;;ACpCO,IAAM,kBAAkB;","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@nexart/signals",
3
+ "version": "0.1.0",
4
+ "description": "Minimal, protocol-agnostic signal capture SDK for optional CER context evidence",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "build:tsc": "tsc --project tsconfig.json --outDir dist-tsc",
29
+ "test": "npm run build:tsc && node --test --test-concurrency 1 dist-tsc/__tests__/signals.test.js",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "nexart",
34
+ "signals",
35
+ "cer",
36
+ "ai",
37
+ "audit",
38
+ "tracing"
39
+ ],
40
+ "license": "MIT"
41
+ }