@indigoai-us/hq-cloud 5.24.0 → 5.26.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 (66) hide show
  1. package/dist/bin/sync-runner.d.ts +151 -17
  2. package/dist/bin/sync-runner.d.ts.map +1 -1
  3. package/dist/bin/sync-runner.js +280 -18
  4. package/dist/bin/sync-runner.js.map +1 -1
  5. package/dist/bin/sync-runner.test.js +429 -15
  6. package/dist/bin/sync-runner.test.js.map +1 -1
  7. package/dist/cli/share.d.ts +9 -0
  8. package/dist/cli/share.d.ts.map +1 -1
  9. package/dist/cli/share.js +54 -1
  10. package/dist/cli/share.js.map +1 -1
  11. package/dist/cli/share.test.js +6 -3
  12. package/dist/cli/share.test.js.map +1 -1
  13. package/dist/cli/sync.d.ts +21 -0
  14. package/dist/cli/sync.d.ts.map +1 -1
  15. package/dist/cli/sync.js.map +1 -1
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +6 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/personal-vault-exclusions.d.ts +128 -0
  21. package/dist/personal-vault-exclusions.d.ts.map +1 -0
  22. package/dist/personal-vault-exclusions.js +231 -0
  23. package/dist/personal-vault-exclusions.js.map +1 -0
  24. package/dist/personal-vault-exclusions.test.d.ts +22 -0
  25. package/dist/personal-vault-exclusions.test.d.ts.map +1 -0
  26. package/dist/personal-vault-exclusions.test.js +198 -0
  27. package/dist/personal-vault-exclusions.test.js.map +1 -0
  28. package/dist/sync/index.d.ts +11 -0
  29. package/dist/sync/index.d.ts.map +1 -0
  30. package/dist/sync/index.js +9 -0
  31. package/dist/sync/index.js.map +1 -0
  32. package/dist/sync/push-event.d.ts +110 -0
  33. package/dist/sync/push-event.d.ts.map +1 -0
  34. package/dist/sync/push-event.js +153 -0
  35. package/dist/sync/push-event.js.map +1 -0
  36. package/dist/sync/push-event.test.d.ts +15 -0
  37. package/dist/sync/push-event.test.d.ts.map +1 -0
  38. package/dist/sync/push-event.test.js +188 -0
  39. package/dist/sync/push-event.test.js.map +1 -0
  40. package/dist/sync/push-transport.d.ts +67 -0
  41. package/dist/sync/push-transport.d.ts.map +1 -0
  42. package/dist/sync/push-transport.js +66 -0
  43. package/dist/sync/push-transport.js.map +1 -0
  44. package/dist/watcher.d.ts +160 -0
  45. package/dist/watcher.d.ts.map +1 -1
  46. package/dist/watcher.js +298 -0
  47. package/dist/watcher.js.map +1 -1
  48. package/dist/watcher.test.d.ts +2 -0
  49. package/dist/watcher.test.d.ts.map +1 -0
  50. package/dist/watcher.test.js +334 -0
  51. package/dist/watcher.test.js.map +1 -0
  52. package/package.json +3 -2
  53. package/src/bin/sync-runner.test.ts +557 -15
  54. package/src/bin/sync-runner.ts +404 -27
  55. package/src/cli/share.test.ts +8 -3
  56. package/src/cli/share.ts +66 -1
  57. package/src/cli/sync.ts +22 -0
  58. package/src/index.ts +27 -0
  59. package/src/personal-vault-exclusions.test.ts +256 -0
  60. package/src/personal-vault-exclusions.ts +277 -0
  61. package/src/sync/index.ts +19 -0
  62. package/src/sync/push-event.test.ts +224 -0
  63. package/src/sync/push-event.ts +208 -0
  64. package/src/sync/push-transport.ts +84 -0
  65. package/src/watcher.test.ts +388 -0
  66. package/src/watcher.ts +386 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * PushEvent — the wire-shared payload exchanged between every link in the
3
+ * event-driven-hq-cloud-sync pipeline.
4
+ *
5
+ * Producers (the watcher) emit one PushEvent per local content change.
6
+ * Consumers (the push endpoint / receiver / coalescer) decode the payload,
7
+ * validate it, and act on it. Because the same shape crosses a network
8
+ * boundary, it has its own dedicated module.
9
+ *
10
+ * Conventions
11
+ * ───────────
12
+ * - `contentHash` is `sha256:<64-lowercase-hex>`. The `<algorithm>:<hex>`
13
+ * prefix lets a future hash migration ship without breaking the wire
14
+ * format — consumers can branch on the prefix and fall back to refusing
15
+ * unknown algorithms.
16
+ * - `mtime` and `eventTimestamp` are strict ISO-8601 datetime strings.
17
+ * `mtime` is the filesystem modification time of the source file at the
18
+ * moment of capture; `eventTimestamp` is when the watcher emitted the
19
+ * event. They diverge whenever the watcher coalesces or retries.
20
+ * - `sequenceNumber` is a non-negative safe integer. Monotonicity per
21
+ * `originDeviceId` is a producer-side invariant — a single PushEvent
22
+ * can't be self-monotonic, so the schema only validates the bounds.
23
+ * - Unknown extra fields are dropped silently by `decodePushEvent` to
24
+ * permit forwards-compatible additions on the producer side without
25
+ * forcing every consumer to upgrade in lockstep.
26
+ *
27
+ * Ported from indigoai-us/hq-pro PR #112 (src/sync/push-event.ts) into
28
+ * @indigoai-us/hq-cloud (Path B) per project event-driven-sync-menubar US-007.
29
+ */
30
+ import { z } from "zod";
31
+ /**
32
+ * `sha256:` + 64 lowercase hex chars. The algorithm prefix is mandatory so
33
+ * future hash migrations stay non-breaking; consumers can switch on the
34
+ * prefix and reject unknown algorithms explicitly.
35
+ */
36
+ export declare const CONTENT_HASH_PATTERN: RegExp;
37
+ /**
38
+ * Strict ISO-8601 datetime regex matching what `z.iso.datetime()` produces.
39
+ *
40
+ * Format: `YYYY-MM-DDTHH:MM:SS(.fff)?(Z|±HH:MM)`
41
+ */
42
+ export declare const ISO8601_DATETIME_PATTERN: RegExp;
43
+ /**
44
+ * Runtime schema for PushEvent. Unknown extra keys are dropped via the zod
45
+ * default `.strip()` behavior — that is load-bearing for forwards
46
+ * compatibility (see module JSDoc).
47
+ */
48
+ export declare const PushEventSchema: z.ZodObject<{
49
+ relativePath: z.ZodString;
50
+ contentHash: z.ZodString;
51
+ mtime: z.ZodString;
52
+ originDeviceId: z.ZodString;
53
+ originTenantId: z.ZodString;
54
+ sequenceNumber: z.ZodNumber;
55
+ eventTimestamp: z.ZodString;
56
+ }, z.core.$strip>;
57
+ /**
58
+ * The canonical PushEvent type. All fields are required; producers and
59
+ * consumers share this exact shape.
60
+ */
61
+ export type PushEvent = z.infer<typeof PushEventSchema>;
62
+ /**
63
+ * Public alias for a single decode issue. Aliased here (rather than re-exporting
64
+ * the `$`-prefixed zod internal symbol directly) so consumers can annotate
65
+ * against `PushEventDecodeIssue` without importing zod internals. The alias is
66
+ * structurally identical to `z.core.$ZodIssue`, so this is a non-breaking
67
+ * narrowing of the public surface.
68
+ */
69
+ export type PushEventDecodeIssue = z.core.$ZodIssue;
70
+ /**
71
+ * Thrown by `decodePushEvent` (and `encodePushEvent` on invalid input) when
72
+ * the payload fails validation. `.issues` carries the underlying zod issues
73
+ * so callers can render structured diagnostics — see the test suite for an
74
+ * example of asserting on the issue path.
75
+ */
76
+ export declare class PushEventDecodeError extends Error {
77
+ readonly issues: readonly PushEventDecodeIssue[];
78
+ readonly stage: "json-parse" | "schema-validation";
79
+ constructor(message: string, args: {
80
+ issues: readonly PushEventDecodeIssue[];
81
+ stage: "json-parse" | "schema-validation";
82
+ cause?: unknown;
83
+ });
84
+ }
85
+ /**
86
+ * Validate `event` against `PushEventSchema` and return the canonical JSON
87
+ * serialization. Validating on encode catches producer-side mistakes early
88
+ * (and ensures the output always round-trips through `decodePushEvent`).
89
+ *
90
+ * Extra keys on `event` are dropped — the returned JSON string contains
91
+ * only the declared PushEvent fields.
92
+ */
93
+ export declare function encodePushEvent(event: PushEvent): string;
94
+ /**
95
+ * Parse and validate an incoming PushEvent. Accepts either a raw JSON string
96
+ * (the on-the-wire form) or an already-parsed object (handy for in-process
97
+ * wiring and tests).
98
+ *
99
+ * - Unknown extra fields are dropped silently — see module JSDoc.
100
+ * - Missing required fields throw `PushEventDecodeError` whose `.issues`
101
+ * exposes the underlying zod issues.
102
+ * - Malformed JSON throws `PushEventDecodeError` with `stage:"json-parse"`
103
+ * and a synthetic issue at the root path.
104
+ * - A JSON string that parses to a non-object value (e.g. `'42'`, `'"text"'`,
105
+ * `'null'`) surfaces as `stage: 'schema-validation'` — the JSON itself was
106
+ * syntactically valid, so it clears the parse stage before failing the
107
+ * object-shape check.
108
+ */
109
+ export declare function decodePushEvent(input: unknown): PushEvent;
110
+ //# sourceMappingURL=push-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-event.d.ts","sourceRoot":"","sources":["../../src/sync/push-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAA0B,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAC+B,CAAC;AAIrE;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;;;;;;iBAqClB,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;AAEpD;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,mBAAmB,CAAC;gBAGjD,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;QACJ,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;QACxC,KAAK,EAAE,YAAY,GAAG,mBAAmB,CAAC;QAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CAOJ;AAID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CASxD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAkCzD"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * PushEvent — the wire-shared payload exchanged between every link in the
3
+ * event-driven-hq-cloud-sync pipeline.
4
+ *
5
+ * Producers (the watcher) emit one PushEvent per local content change.
6
+ * Consumers (the push endpoint / receiver / coalescer) decode the payload,
7
+ * validate it, and act on it. Because the same shape crosses a network
8
+ * boundary, it has its own dedicated module.
9
+ *
10
+ * Conventions
11
+ * ───────────
12
+ * - `contentHash` is `sha256:<64-lowercase-hex>`. The `<algorithm>:<hex>`
13
+ * prefix lets a future hash migration ship without breaking the wire
14
+ * format — consumers can branch on the prefix and fall back to refusing
15
+ * unknown algorithms.
16
+ * - `mtime` and `eventTimestamp` are strict ISO-8601 datetime strings.
17
+ * `mtime` is the filesystem modification time of the source file at the
18
+ * moment of capture; `eventTimestamp` is when the watcher emitted the
19
+ * event. They diverge whenever the watcher coalesces or retries.
20
+ * - `sequenceNumber` is a non-negative safe integer. Monotonicity per
21
+ * `originDeviceId` is a producer-side invariant — a single PushEvent
22
+ * can't be self-monotonic, so the schema only validates the bounds.
23
+ * - Unknown extra fields are dropped silently by `decodePushEvent` to
24
+ * permit forwards-compatible additions on the producer side without
25
+ * forcing every consumer to upgrade in lockstep.
26
+ *
27
+ * Ported from indigoai-us/hq-pro PR #112 (src/sync/push-event.ts) into
28
+ * @indigoai-us/hq-cloud (Path B) per project event-driven-sync-menubar US-007.
29
+ */
30
+ import { z } from "zod";
31
+ // ─── Constants ─────────────────────────────────────────────────────────────
32
+ /**
33
+ * `sha256:` + 64 lowercase hex chars. The algorithm prefix is mandatory so
34
+ * future hash migrations stay non-breaking; consumers can switch on the
35
+ * prefix and reject unknown algorithms explicitly.
36
+ */
37
+ export const CONTENT_HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
38
+ /**
39
+ * Strict ISO-8601 datetime regex matching what `z.iso.datetime()` produces.
40
+ *
41
+ * Format: `YYYY-MM-DDTHH:MM:SS(.fff)?(Z|±HH:MM)`
42
+ */
43
+ export const ISO8601_DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
44
+ // ─── Zod schema ────────────────────────────────────────────────────────────
45
+ /**
46
+ * Runtime schema for PushEvent. Unknown extra keys are dropped via the zod
47
+ * default `.strip()` behavior — that is load-bearing for forwards
48
+ * compatibility (see module JSDoc).
49
+ */
50
+ export const PushEventSchema = z
51
+ .object({
52
+ relativePath: z
53
+ .string()
54
+ .min(1, "relativePath must be a non-empty string"),
55
+ contentHash: z
56
+ .string()
57
+ .regex(CONTENT_HASH_PATTERN, "contentHash must match `sha256:<64 lowercase hex>`"),
58
+ mtime: z
59
+ .string()
60
+ .regex(ISO8601_DATETIME_PATTERN, "mtime must be an ISO-8601 datetime"),
61
+ originDeviceId: z
62
+ .string()
63
+ .min(1, "originDeviceId must be a non-empty string"),
64
+ originTenantId: z
65
+ .string()
66
+ .min(1, "originTenantId must be a non-empty string"),
67
+ sequenceNumber: z
68
+ .number()
69
+ .int("sequenceNumber must be an integer")
70
+ .min(0, "sequenceNumber must be non-negative")
71
+ .max(Number.MAX_SAFE_INTEGER, "sequenceNumber must be <= Number.MAX_SAFE_INTEGER"),
72
+ eventTimestamp: z
73
+ .string()
74
+ .regex(ISO8601_DATETIME_PATTERN, "eventTimestamp must be an ISO-8601 datetime"),
75
+ })
76
+ // `.strip()` is the zod 4 default; called explicitly here so the intent is
77
+ // obvious to future readers. Unknown keys MUST NOT throw — see module JSDoc.
78
+ .strip();
79
+ /**
80
+ * Thrown by `decodePushEvent` (and `encodePushEvent` on invalid input) when
81
+ * the payload fails validation. `.issues` carries the underlying zod issues
82
+ * so callers can render structured diagnostics — see the test suite for an
83
+ * example of asserting on the issue path.
84
+ */
85
+ export class PushEventDecodeError extends Error {
86
+ issues;
87
+ stage;
88
+ constructor(message, args) {
89
+ super(message, args.cause === undefined ? undefined : { cause: args.cause });
90
+ this.name = "PushEventDecodeError";
91
+ this.issues = args.issues;
92
+ this.stage = args.stage;
93
+ }
94
+ }
95
+ // ─── Encode / Decode ───────────────────────────────────────────────────────
96
+ /**
97
+ * Validate `event` against `PushEventSchema` and return the canonical JSON
98
+ * serialization. Validating on encode catches producer-side mistakes early
99
+ * (and ensures the output always round-trips through `decodePushEvent`).
100
+ *
101
+ * Extra keys on `event` are dropped — the returned JSON string contains
102
+ * only the declared PushEvent fields.
103
+ */
104
+ export function encodePushEvent(event) {
105
+ const parsed = PushEventSchema.safeParse(event);
106
+ if (!parsed.success) {
107
+ throw new PushEventDecodeError("encodePushEvent: input failed PushEvent schema validation", { issues: parsed.error.issues, stage: "schema-validation" });
108
+ }
109
+ return JSON.stringify(parsed.data);
110
+ }
111
+ /**
112
+ * Parse and validate an incoming PushEvent. Accepts either a raw JSON string
113
+ * (the on-the-wire form) or an already-parsed object (handy for in-process
114
+ * wiring and tests).
115
+ *
116
+ * - Unknown extra fields are dropped silently — see module JSDoc.
117
+ * - Missing required fields throw `PushEventDecodeError` whose `.issues`
118
+ * exposes the underlying zod issues.
119
+ * - Malformed JSON throws `PushEventDecodeError` with `stage:"json-parse"`
120
+ * and a synthetic issue at the root path.
121
+ * - A JSON string that parses to a non-object value (e.g. `'42'`, `'"text"'`,
122
+ * `'null'`) surfaces as `stage: 'schema-validation'` — the JSON itself was
123
+ * syntactically valid, so it clears the parse stage before failing the
124
+ * object-shape check.
125
+ */
126
+ export function decodePushEvent(input) {
127
+ let candidate = input;
128
+ if (typeof input === "string") {
129
+ try {
130
+ candidate = JSON.parse(input);
131
+ }
132
+ catch (err) {
133
+ throw new PushEventDecodeError("decodePushEvent: input string is not valid JSON", {
134
+ issues: [
135
+ {
136
+ code: "custom",
137
+ message: err instanceof Error ? err.message : "invalid JSON input",
138
+ path: [],
139
+ input,
140
+ },
141
+ ],
142
+ stage: "json-parse",
143
+ cause: err,
144
+ });
145
+ }
146
+ }
147
+ const parsed = PushEventSchema.safeParse(candidate);
148
+ if (!parsed.success) {
149
+ throw new PushEventDecodeError("decodePushEvent: payload failed PushEvent schema validation", { issues: parsed.error.issues, stage: "schema-validation" });
150
+ }
151
+ return parsed.data;
152
+ }
153
+ //# sourceMappingURL=push-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-event.js","sourceRoot":"","sources":["../../src/sync/push-event.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Unit tests for `src/sync/push-event.ts` (US-007 port).
3
+ *
4
+ * Covers the three required acceptance assertions:
5
+ * 1. A known-good fixture round-trips through encode → decode unchanged.
6
+ * 2. Unknown extra fields on the input are dropped silently (no throw).
7
+ * 3. Missing required fields throw a typed `PushEventDecodeError` whose
8
+ * `.issues` exposes the underlying zod issues.
9
+ *
10
+ * Also covers the supporting invariants needed to keep the wire contract
11
+ * stable across the watcher → server → receiver hop: malformed JSON, bad
12
+ * hash/timestamp formats, and the integer/range bounds on `sequenceNumber`.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=push-event.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-event.test.d.ts","sourceRoot":"","sources":["../../src/sync/push-event.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Unit tests for `src/sync/push-event.ts` (US-007 port).
3
+ *
4
+ * Covers the three required acceptance assertions:
5
+ * 1. A known-good fixture round-trips through encode → decode unchanged.
6
+ * 2. Unknown extra fields on the input are dropped silently (no throw).
7
+ * 3. Missing required fields throw a typed `PushEventDecodeError` whose
8
+ * `.issues` exposes the underlying zod issues.
9
+ *
10
+ * Also covers the supporting invariants needed to keep the wire contract
11
+ * stable across the watcher → server → receiver hop: malformed JSON, bad
12
+ * hash/timestamp formats, and the integer/range bounds on `sequenceNumber`.
13
+ */
14
+ import { describe, expect, it } from "vitest";
15
+ import { PushEventDecodeError, decodePushEvent, encodePushEvent, } from "../../src/sync/index.js";
16
+ // A canonical, valid PushEvent. All other tests derive from this fixture so
17
+ // any single field mutation can't accidentally pass for the wrong reason.
18
+ const validFixture = {
19
+ relativePath: "docs/architecture/overview.md",
20
+ contentHash: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
21
+ mtime: "2026-05-18T12:34:56.789Z",
22
+ originDeviceId: "device-laptop-a",
23
+ originTenantId: "tenant-indigo",
24
+ sequenceNumber: 42,
25
+ eventTimestamp: "2026-05-18T12:35:00.000Z",
26
+ };
27
+ describe("PushEvent encode/decode", () => {
28
+ // ── Acceptance #1: round-trip ──────────────────────────────────────────
29
+ it("round-trips a known-good fixture through encode → decode", () => {
30
+ const encoded = encodePushEvent(validFixture);
31
+ expect(typeof encoded).toBe("string");
32
+ const decoded = decodePushEvent(encoded);
33
+ expect(decoded).toEqual(validFixture);
34
+ // Re-encoding the decoded value must produce byte-identical output —
35
+ // catches accidental field re-ordering or coercion drift.
36
+ expect(encodePushEvent(decoded)).toBe(encoded);
37
+ });
38
+ it("decodes from a pre-parsed object as well as from a JSON string", () => {
39
+ const fromObject = decodePushEvent({ ...validFixture });
40
+ const fromString = decodePushEvent(JSON.stringify(validFixture));
41
+ expect(fromObject).toEqual(validFixture);
42
+ expect(fromString).toEqual(validFixture);
43
+ });
44
+ // ── Acceptance #2: unknown fields dropped ──────────────────────────────
45
+ it("drops unknown extra fields silently (does not throw)", () => {
46
+ const withExtras = {
47
+ ...validFixture,
48
+ // Future producers may add fields like `originAppVersion`; today's
49
+ // consumers must not crash on them.
50
+ originAppVersion: "1.2.3",
51
+ experimentalFlag: true,
52
+ nested: { ignored: "yes" },
53
+ };
54
+ const decoded = decodePushEvent(withExtras);
55
+ expect(decoded).toEqual(validFixture);
56
+ expect(decoded).not.toHaveProperty("originAppVersion");
57
+ expect(decoded).not.toHaveProperty("experimentalFlag");
58
+ expect(decoded).not.toHaveProperty("nested");
59
+ // Round-tripping through JSON behaves the same way.
60
+ const decodedFromJson = decodePushEvent(JSON.stringify(withExtras));
61
+ expect(decodedFromJson).toEqual(validFixture);
62
+ });
63
+ // ── Acceptance #3: missing required fields throw typed error ───────────
64
+ it.each([
65
+ "relativePath",
66
+ "contentHash",
67
+ "mtime",
68
+ "originDeviceId",
69
+ "originTenantId",
70
+ "sequenceNumber",
71
+ "eventTimestamp",
72
+ ])("throws PushEventDecodeError when %s is missing", (field) => {
73
+ const partial = { ...validFixture };
74
+ delete partial[field];
75
+ let caught;
76
+ try {
77
+ decodePushEvent(partial);
78
+ }
79
+ catch (err) {
80
+ caught = err;
81
+ }
82
+ expect(caught).toBeInstanceOf(PushEventDecodeError);
83
+ const error = caught;
84
+ expect(error.stage).toBe("schema-validation");
85
+ expect(error.issues.length).toBeGreaterThan(0);
86
+ // The zod issue path must point at the missing field — that's what
87
+ // downstream callers rely on to render structured diagnostics.
88
+ expect(error.issues.some((issue) => issue.path.includes(field))).toBe(true);
89
+ });
90
+ it("PushEventDecodeError carries the underlying zod issues array", () => {
91
+ // Multiple missing fields → multiple issues, all reachable via `.issues`.
92
+ const sparse = { relativePath: "x.md" };
93
+ let caught;
94
+ try {
95
+ decodePushEvent(sparse);
96
+ }
97
+ catch (err) {
98
+ caught = err;
99
+ }
100
+ expect(caught).toBeInstanceOf(PushEventDecodeError);
101
+ const error = caught;
102
+ expect(error.issues.length).toBeGreaterThanOrEqual(6);
103
+ });
104
+ // ── Supporting wire-contract invariants ────────────────────────────────
105
+ it("throws PushEventDecodeError on malformed JSON input", () => {
106
+ let caught;
107
+ try {
108
+ decodePushEvent("{not valid json");
109
+ }
110
+ catch (err) {
111
+ caught = err;
112
+ }
113
+ expect(caught).toBeInstanceOf(PushEventDecodeError);
114
+ const error = caught;
115
+ expect(error.stage).toBe("json-parse");
116
+ expect(error.issues.length).toBe(1);
117
+ });
118
+ it("surfaces a JSON-parseable non-object payload as schema-validation, not json-parse", () => {
119
+ // `'42'` is syntactically valid JSON, so the parse stage clears; the
120
+ // object-shape check is what fails. Pinning this here keeps the JSDoc
121
+ // contract (see decodePushEvent docs) honest.
122
+ let caught;
123
+ try {
124
+ decodePushEvent("42");
125
+ }
126
+ catch (err) {
127
+ caught = err;
128
+ }
129
+ expect(caught).toBeInstanceOf(PushEventDecodeError);
130
+ const error = caught;
131
+ expect(error.stage).toBe("schema-validation");
132
+ });
133
+ it.each([
134
+ ["raw hex without `sha256:` prefix", "e".repeat(64)],
135
+ ["wrong algorithm prefix", `md5:${"a".repeat(64)}`],
136
+ ["uppercase hex", `sha256:${"A".repeat(64)}`],
137
+ ["too few hex chars", `sha256:${"a".repeat(63)}`],
138
+ ])("rejects contentHash: %s", (_label, badHash) => {
139
+ expect(() => decodePushEvent({ ...validFixture, contentHash: badHash })).toThrow(PushEventDecodeError);
140
+ });
141
+ it.each([
142
+ ["missing timezone", "2026-05-18T12:34:56.789"],
143
+ ["space separator", "2026-05-18 12:34:56Z"],
144
+ ["date only", "2026-05-18"],
145
+ ])("rejects ISO-8601 timestamps: %s", (_label, badTimestamp) => {
146
+ expect(() => decodePushEvent({ ...validFixture, mtime: badTimestamp })).toThrow(PushEventDecodeError);
147
+ expect(() => decodePushEvent({ ...validFixture, eventTimestamp: badTimestamp })).toThrow(PushEventDecodeError);
148
+ });
149
+ it("accepts a sequenceNumber of 0 and rejects negative / fractional / oversized values", () => {
150
+ // 0 is allowed — sequence numbers are non-negative, not strictly positive.
151
+ expect(decodePushEvent({ ...validFixture, sequenceNumber: 0 })).toEqual({
152
+ ...validFixture,
153
+ sequenceNumber: 0,
154
+ });
155
+ expect(() => decodePushEvent({ ...validFixture, sequenceNumber: -1 })).toThrow(PushEventDecodeError);
156
+ expect(() => decodePushEvent({ ...validFixture, sequenceNumber: 1.5 })).toThrow(PushEventDecodeError);
157
+ expect(() => decodePushEvent({
158
+ ...validFixture,
159
+ sequenceNumber: Number.MAX_SAFE_INTEGER + 1,
160
+ })).toThrow(PushEventDecodeError);
161
+ });
162
+ it("encodePushEvent validates and drops unknown fields from the output", () => {
163
+ // We cast through `unknown` because the public type forbids extra keys —
164
+ // this models a producer that hands us a wider object by mistake.
165
+ const wider = {
166
+ ...validFixture,
167
+ stray: "field",
168
+ };
169
+ const encoded = encodePushEvent(wider);
170
+ expect(encoded.includes("stray")).toBe(false);
171
+ expect(JSON.parse(encoded)).toEqual(validFixture);
172
+ });
173
+ it("encodePushEvent throws PushEventDecodeError when input is invalid", () => {
174
+ const bad = { ...validFixture, contentHash: "not-a-hash" };
175
+ let caught;
176
+ try {
177
+ encodePushEvent(bad);
178
+ }
179
+ catch (err) {
180
+ caught = err;
181
+ }
182
+ expect(caught).toBeInstanceOf(PushEventDecodeError);
183
+ const error = caught;
184
+ expect(error.stage).toBe("schema-validation");
185
+ expect(error.issues.some((issue) => issue.path.includes("contentHash"))).toBe(true);
186
+ });
187
+ });
188
+ //# sourceMappingURL=push-event.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-event.test.js","sourceRoot":"","sources":["../../src/sync/push-event.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * PushTransport — outbound shipping seam for the watcher daemon.
3
+ *
4
+ * The daemon wires file-watcher events into a transport that ships each
5
+ * PushEvent to the cloud. Defining the interface here lets the daemon ship
6
+ * with a swappable boundary — tests inject a fake, a later story swaps in a
7
+ * concrete WebSocket/HTTP implementation, and the daemon entry point never
8
+ * changes.
9
+ *
10
+ * Lifecycle
11
+ * ─────────
12
+ * - `start()` is awaited BEFORE the watcher is started. It's the place
13
+ * to open sockets, refresh tokens, etc.
14
+ * - `publish(event)` is called for every coalesced PushEvent the watcher
15
+ * emits. Implementations decide whether to buffer, batch, or send
16
+ * inline; the daemon awaits the returned promise so back-pressure can
17
+ * be honored.
18
+ * - `dispose()` is awaited DURING shutdown, AFTER the watcher has been
19
+ * torn down. Implementations should drain in-flight publishes (with
20
+ * their own internal timeout) and close any sockets.
21
+ * - `connected` is a passive boolean used by the health endpoint. It MAY
22
+ * flap during reconnect attempts — that's fine; consumers treat it as
23
+ * advisory, not a contract.
24
+ *
25
+ * The `NoopPushTransport` shipped here is the default when no transport
26
+ * is wired in: it counts publishes (so unit tests can assert delivery)
27
+ * and logs nothing — observers should rely on the watcher's `onEvent`
28
+ * counter on the daemon side, not the transport's internals.
29
+ *
30
+ * Ported from indigoai-us/hq-pro PR #112 (src/sync/push-transport.ts) into
31
+ * @indigoai-us/hq-cloud (Path B) per project event-driven-sync-menubar US-007.
32
+ */
33
+ import type { PushEvent } from "./push-event.js";
34
+ export interface PushTransport {
35
+ /** Open sockets, refresh tokens. Awaited before the watcher starts. */
36
+ start(): Promise<void>;
37
+ /** Ship one coalesced PushEvent. Awaited per event for back-pressure. */
38
+ publish(event: PushEvent): Promise<void>;
39
+ /** Drain + close. Awaited during daemon shutdown after watcher.dispose. */
40
+ dispose(): Promise<void>;
41
+ /** Advisory: is the transport currently believed to be connected? */
42
+ readonly connected: boolean;
43
+ }
44
+ /**
45
+ * Default `PushTransport` used until a real implementation lands.
46
+ *
47
+ * Behavior:
48
+ * - `start()` flips `connected` to true.
49
+ * - `publish()` increments a counter (visible via `publishedCount`).
50
+ * - `dispose()` flips `connected` back to false.
51
+ *
52
+ * Deliberately silent: when the daemon runs with this default, the
53
+ * watcher's per-event log line (emitted by the daemon itself, not the
54
+ * transport) is the only observability. That keeps the noop from drowning
55
+ * the log when high-rate writes hit a dev machine.
56
+ */
57
+ export declare class NoopPushTransport implements PushTransport {
58
+ private _connected;
59
+ private _count;
60
+ get connected(): boolean;
61
+ /** Test/observability hook: how many events have been published. */
62
+ get publishedCount(): number;
63
+ start(): Promise<void>;
64
+ publish(_event: PushEvent): Promise<void>;
65
+ dispose(): Promise<void>;
66
+ }
67
+ //# sourceMappingURL=push-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-transport.d.ts","sourceRoot":"","sources":["../../src/sync/push-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,yEAAyE;IACzE,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,2EAA2E;IAC3E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAK;IAEnB,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,oEAAoE;IACpE,IAAI,cAAc,IAAI,MAAM,CAE3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,OAAO,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * PushTransport — outbound shipping seam for the watcher daemon.
3
+ *
4
+ * The daemon wires file-watcher events into a transport that ships each
5
+ * PushEvent to the cloud. Defining the interface here lets the daemon ship
6
+ * with a swappable boundary — tests inject a fake, a later story swaps in a
7
+ * concrete WebSocket/HTTP implementation, and the daemon entry point never
8
+ * changes.
9
+ *
10
+ * Lifecycle
11
+ * ─────────
12
+ * - `start()` is awaited BEFORE the watcher is started. It's the place
13
+ * to open sockets, refresh tokens, etc.
14
+ * - `publish(event)` is called for every coalesced PushEvent the watcher
15
+ * emits. Implementations decide whether to buffer, batch, or send
16
+ * inline; the daemon awaits the returned promise so back-pressure can
17
+ * be honored.
18
+ * - `dispose()` is awaited DURING shutdown, AFTER the watcher has been
19
+ * torn down. Implementations should drain in-flight publishes (with
20
+ * their own internal timeout) and close any sockets.
21
+ * - `connected` is a passive boolean used by the health endpoint. It MAY
22
+ * flap during reconnect attempts — that's fine; consumers treat it as
23
+ * advisory, not a contract.
24
+ *
25
+ * The `NoopPushTransport` shipped here is the default when no transport
26
+ * is wired in: it counts publishes (so unit tests can assert delivery)
27
+ * and logs nothing — observers should rely on the watcher's `onEvent`
28
+ * counter on the daemon side, not the transport's internals.
29
+ *
30
+ * Ported from indigoai-us/hq-pro PR #112 (src/sync/push-transport.ts) into
31
+ * @indigoai-us/hq-cloud (Path B) per project event-driven-sync-menubar US-007.
32
+ */
33
+ /**
34
+ * Default `PushTransport` used until a real implementation lands.
35
+ *
36
+ * Behavior:
37
+ * - `start()` flips `connected` to true.
38
+ * - `publish()` increments a counter (visible via `publishedCount`).
39
+ * - `dispose()` flips `connected` back to false.
40
+ *
41
+ * Deliberately silent: when the daemon runs with this default, the
42
+ * watcher's per-event log line (emitted by the daemon itself, not the
43
+ * transport) is the only observability. That keeps the noop from drowning
44
+ * the log when high-rate writes hit a dev machine.
45
+ */
46
+ export class NoopPushTransport {
47
+ _connected = false;
48
+ _count = 0;
49
+ get connected() {
50
+ return this._connected;
51
+ }
52
+ /** Test/observability hook: how many events have been published. */
53
+ get publishedCount() {
54
+ return this._count;
55
+ }
56
+ async start() {
57
+ this._connected = true;
58
+ }
59
+ async publish(_event) {
60
+ this._count += 1;
61
+ }
62
+ async dispose() {
63
+ this._connected = false;
64
+ }
65
+ }
66
+ //# sourceMappingURL=push-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-transport.js","sourceRoot":"","sources":["../../src/sync/push-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAeH;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,iBAAiB;IACpB,UAAU,GAAG,KAAK,CAAC;IACnB,MAAM,GAAG,CAAC,CAAC;IAEnB,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,oEAAoE;IACpE,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAiB;QAC7B,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;CACF"}