jinaga-server 3.5.4 → 3.6.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 (40) hide show
  1. package/.github/workflows/publish.yml +5 -2
  2. package/.github/workflows/upgrade-jinaga.yml +51 -0
  3. package/CLAUDE.md +77 -0
  4. package/dist/authorization/authorization-keystore.d.ts +27 -1
  5. package/dist/authorization/authorization-keystore.js +52 -4
  6. package/dist/authorization/authorization-keystore.js.map +1 -1
  7. package/dist/http/router.d.ts +4 -1
  8. package/dist/http/router.js +157 -14
  9. package/dist/http/router.js.map +1 -1
  10. package/dist/jinaga-server.js +18 -1
  11. package/dist/jinaga-server.js.map +1 -1
  12. package/dist/memory/memory-keystore.js +1 -1
  13. package/dist/memory/memory-keystore.js.map +1 -1
  14. package/dist/postgres/query-description.d.ts +1 -0
  15. package/dist/postgres/query-description.js +34 -3
  16. package/dist/postgres/query-description.js.map +1 -1
  17. package/dist/postgres/specification-sql.js +7 -1
  18. package/dist/postgres/specification-sql.js.map +1 -1
  19. package/docs/analysis/feed-builder-nested-existential-bug.md +191 -0
  20. package/integration-test/jinaga-test/distribution-multi-given.test.js +184 -0
  21. package/integration-test/jinaga-test/distribution-negating-feed.test.js +202 -0
  22. package/integration-test/jinaga-test/feed-existential-input.test.js +241 -0
  23. package/integration-test/jinaga-test/feeds-endpoint.test.js +140 -0
  24. package/integration-test/jinaga-test/package-lock.json +4 -4
  25. package/integration-test/jinaga-test/package.json +3 -2
  26. package/integration-test/jinaga-test/subscription-anchor-arrival.test.js +129 -0
  27. package/integration-test/jinaga-test/subscription-helpers.js +60 -0
  28. package/integration-test/jinaga-test/subscription-late-auth.test.js +245 -0
  29. package/integration-test/jinaga-test/subscription-no-rule.test.js +137 -0
  30. package/package.json +2 -2
  31. package/src/authorization/authorization-keystore.ts +87 -5
  32. package/src/http/router.ts +174 -21
  33. package/src/jinaga-server.ts +36 -3
  34. package/src/memory/memory-keystore.ts +1 -1
  35. package/src/postgres/query-description.ts +66 -11
  36. package/src/postgres/specification-sql.ts +7 -1
  37. package/test/authorization/subscriptionIntersectionSpec.ts +208 -0
  38. package/test/http/anchorListenerSpec.ts +117 -0
  39. package/test/http/lateAuthRecoverySpec.ts +245 -0
  40. package/test/http/streamFeedAnchorSpec.ts +169 -0
@@ -7,14 +7,17 @@ on:
7
7
  jobs:
8
8
  build:
9
9
  runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write
10
13
  steps:
11
14
  - uses: actions/checkout@v4
12
- # Setup .npmrc file to publish to npm
13
15
  - uses: actions/setup-node@v4
14
16
  with:
15
17
  node-version: '20.x'
16
18
  registry-url: 'https://registry.npmjs.org'
19
+ - run: npm install -g npm@latest
17
20
  - run: npm install
18
21
  - run: npm publish
19
22
  env:
20
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
23
+ NODE_AUTH_TOKEN: ''
@@ -0,0 +1,51 @@
1
+ name: Upgrade jinaga
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 6 * * *' # daily at 06:00 UTC
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ upgrade:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20.x'
22
+
23
+ - name: Get versions
24
+ id: versions
25
+ run: |
26
+ CURRENT=$(node -p "require('./package.json').dependencies.jinaga.replace(/[\^~]/, '')")
27
+ LATEST=$(npm view jinaga dist-tags.latest)
28
+ echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
29
+ echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
30
+
31
+ - name: Update root
32
+ if: steps.versions.outputs.current != steps.versions.outputs.latest
33
+ run: npm install jinaga@${{ steps.versions.outputs.latest }}
34
+
35
+ - name: Update integration-test
36
+ if: steps.versions.outputs.current != steps.versions.outputs.latest
37
+ working-directory: integration-test/jinaga-test
38
+ run: npm install jinaga@${{ steps.versions.outputs.latest }}
39
+
40
+ - name: Open PR
41
+ if: steps.versions.outputs.current != steps.versions.outputs.latest
42
+ uses: peter-evans/create-pull-request@v7
43
+ with:
44
+ token: ${{ secrets.GITHUB_TOKEN }}
45
+ branch: upgrade/jinaga-${{ steps.versions.outputs.latest }}
46
+ commit-message: "Upgraded Jinaga to ${{ steps.versions.outputs.latest }}"
47
+ title: "Upgraded Jinaga to ${{ steps.versions.outputs.latest }}"
48
+ body: |
49
+ Automated upgrade of `jinaga` from `${{ steps.versions.outputs.current }}` to `${{ steps.versions.outputs.latest }}`.
50
+ labels: dependencies
51
+ delete-branch: true
package/CLAUDE.md ADDED
@@ -0,0 +1,77 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ npm run build # Compile TypeScript to dist/
9
+ npm test # Type-check + run Jest tests
10
+ npm run test:watch # Jest in watch mode
11
+ npm run clean # Remove dist/ and integration-test artifacts
12
+ npm run integration # Full build + integration tests (requires Postgres)
13
+ npm run debug # Run debug.ts entry point (configure DB in src/debug.ts first)
14
+ ```
15
+
16
+ Run a single test file:
17
+ ```bash
18
+ npx jest test/postgres/postgresReadSpec.ts
19
+ ```
20
+
21
+ Run tests matching a pattern:
22
+ ```bash
23
+ npx jest --testNamePattern="should read"
24
+ ```
25
+
26
+ ## Architecture
27
+
28
+ Jinaga Server is a middle-tier backend that syncs immutable facts between clients and PostgreSQL. Facts are append-only records (never mutated), signed by user/device identities, and queried via declarative Specifications rather than SQL.
29
+
30
+ ### Layers (top to bottom)
31
+
32
+ **HTTP** (`src/http/router.ts`) — Express router with three endpoints:
33
+ - `POST /jinaga/write` — accept FactEnvelopes from clients
34
+ - `POST /jinaga/read` — query facts by Specification
35
+ - `POST /jinaga/feed` — long-lived streaming subscription (`application/x-jinaga-feed-stream`)
36
+
37
+ **Authentication** (`src/authentication/`) — Associates each request with a user/device identity. `AuthenticationDevice` is for server-local operations; `AuthenticationSession` is for per-request user context.
38
+
39
+ **Authorization** (`src/authorization/authorization-keystore.ts`) — Enforces who can write facts and who can receive them, using `AuthorizationEngine` and `DistributionEngine` from the `jinaga` core library.
40
+
41
+ **Core** — `FactManager` from the `jinaga` library handles fact storage coordination. This server implements the `Storage` and `Keystore` interfaces that `FactManager` depends on.
42
+
43
+ **Storage** (`src/postgres/`, `src/memory/`) — Two implementations:
44
+ - `PostgresStore` + `PostgresKeystore` — production persistence in PostgreSQL
45
+ - `MemoryKeystore` (in-memory) — used in tests and development
46
+
47
+ ### Assembly
48
+
49
+ `JinagaServer.create(config)` in `src/jinaga-server.ts` wires everything together via dependency injection. It reads the config to determine which storage strategy, fork strategy (pass-through, transient, or persistent for upstream replication), and authorization/distribution rules to use.
50
+
51
+ ### Specification → SQL
52
+
53
+ The most complex translation in the codebase is in `src/postgres/specification-sql.ts`, which converts a `Specification` object (from the `jinaga` library's query DSL) into SQL JOIN queries. Related: `src/postgres/specification-result-sql.ts` for result projection and `src/postgres/purge-sql.ts` for deletions.
54
+
55
+ ### Feed streaming
56
+
57
+ `src/http/stream.ts` manages long-lived HTTP connections. When new facts arrive that match a subscription, they're pushed to all connected clients via the queue-based event system.
58
+
59
+ ### Fork strategies
60
+
61
+ Forks determine how facts propagate upstream:
62
+ - `PassThroughFork` — no upstream, single-server
63
+ - `TransientFork` — in-memory queue (dev/test)
64
+ - `PersistentFork` — `postgres-queue.ts` backed queue for reliable upstream replication to other Jinaga servers
65
+
66
+ ## Key interfaces
67
+
68
+ - `Storage` (from `jinaga`) — `save()`, `read()`, `feed()`, `remove()`
69
+ - `Keystore` (`src/keystore.ts`) — `getOrCreateUserFact()`, `signFacts()`, etc.
70
+
71
+ ## Database schema
72
+
73
+ `setup.sql` initializes the schema. Key tables: `fact`, `fact_type`, `role`, `ancestor`, `signature`, `public_key`, `user`, `device`. Facts are immutable once written. The `ancestor` table tracks relationships between facts (predecessor/successor edges).
74
+
75
+ ## Testing
76
+
77
+ Tests live in `test/` matching pattern `**/test/**/*Spec.ts`. Tests use the in-memory keystore and often the actual `PostgresStore` against a real database for integration-level unit tests. See `test/models/blog.ts` for the canonical test data model.
@@ -1,6 +1,29 @@
1
1
  import { Authorization, AuthorizationRules, DistributionRules, FactEnvelope, FactFeed, FactManager, FactReference, ReferencesByName, Specification, Storage, UserIdentity } from "jinaga";
2
+ export interface DistributionIntersectionBranch {
3
+ start: FactReference[];
4
+ specification: Specification;
5
+ }
6
+ export type DistributionBranchesResult = {
7
+ type: "success";
8
+ branches: DistributionIntersectionBranch[];
9
+ } | {
10
+ type: "denied";
11
+ reason: string;
12
+ };
13
+ export type FeedResult = {
14
+ type: "success";
15
+ feed: FactFeed;
16
+ } | {
17
+ type: "denied";
18
+ reason: string;
19
+ };
20
+ export interface SubscriptionAuthorizer {
21
+ verifyDistributionOrIntersect(userIdentity: UserIdentity | null, specification: Specification, namedStart: ReferencesByName): Promise<DistributionBranchesResult>;
22
+ feedPreVerified(userIdentity: UserIdentity | null, specification: Specification, start: FactReference[], bookmark: string): Promise<FactFeed>;
23
+ feedWithDistribution(userIdentity: UserIdentity | null, specification: Specification, start: FactReference[], bookmark: string): Promise<FeedResult>;
24
+ }
2
25
  import { Keystore } from "../keystore";
3
- export declare class AuthorizationKeystore implements Authorization {
26
+ export declare class AuthorizationKeystore implements Authorization, SubscriptionAuthorizer {
4
27
  private factManager;
5
28
  private store;
6
29
  private keystore;
@@ -11,7 +34,10 @@ export declare class AuthorizationKeystore implements Authorization {
11
34
  getOrCreateUserFact(userIdentity: UserIdentity): Promise<import("jinaga").FactRecord>;
12
35
  read(userIdentity: UserIdentity | null, start: FactReference[], specification: Specification): Promise<import("jinaga").ProjectedResult[]>;
13
36
  feed(userIdentity: UserIdentity | null, specification: Specification, start: FactReference[], bookmark: string): Promise<FactFeed>;
37
+ feedWithDistribution(userIdentity: UserIdentity | null, specification: Specification, start: FactReference[], bookmark: string): Promise<FeedResult>;
38
+ feedPreVerified(userIdentity: UserIdentity | null, specification: Specification, start: FactReference[], bookmark: string): Promise<FactFeed>;
14
39
  load(userIdentity: UserIdentity, references: FactReference[]): Promise<FactEnvelope[]>;
15
40
  save(userIdentity: UserIdentity | null, envelopes: FactEnvelope[]): Promise<FactEnvelope[]>;
16
41
  verifyDistribution(userIdentity: UserIdentity | null, feeds: Specification[], namedStart: ReferencesByName): Promise<void>;
42
+ verifyDistributionOrIntersect(userIdentity: UserIdentity | null, specification: Specification, namedStart: ReferencesByName): Promise<DistributionBranchesResult>;
17
43
  }
@@ -54,6 +54,15 @@ class AuthorizationKeystore {
54
54
  });
55
55
  }
56
56
  feed(userIdentity, specification, start, bookmark) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const result = yield this.feedWithDistribution(userIdentity, specification, start, bookmark);
59
+ if (result.type === "denied") {
60
+ throw new jinaga_1.Forbidden(result.reason);
61
+ }
62
+ return result.feed;
63
+ });
64
+ }
65
+ feedWithDistribution(userIdentity, specification, start, bookmark) {
57
66
  return __awaiter(this, void 0, void 0, function* () {
58
67
  if (this.distributionEngine) {
59
68
  const userReference = userIdentity
@@ -62,18 +71,36 @@ class AuthorizationKeystore {
62
71
  const namedStart = specification.given.reduce((map, g, index) => (Object.assign(Object.assign({}, map), { [g.label.name]: start[index] })), {});
63
72
  const canDistribute = yield this.distributionEngine.canDistributeToAll([specification], namedStart, userReference);
64
73
  if (canDistribute.type === "failure") {
65
- throw new jinaga_1.Forbidden(canDistribute.reason);
74
+ return { type: "denied", reason: canDistribute.reason };
66
75
  }
67
76
  const factFeed = yield this.store.feed(specification, start, bookmark);
68
77
  const factReferences = factFeed.tuples
69
78
  .flatMap(tuple => tuple.facts)
70
79
  .filter((value, index, self) => self.findIndex((0, jinaga_1.factReferenceEquals)(value)) === index);
71
80
  this.distributedFacts.add(factReferences, userReference);
72
- return factFeed;
81
+ return { type: "success", feed: factFeed };
73
82
  }
74
- else {
75
- return yield this.store.feed(specification, start, bookmark);
83
+ return { type: "success", feed: yield this.store.feed(specification, start, bookmark) };
84
+ });
85
+ }
86
+ feedPreVerified(userIdentity, specification, start, bookmark) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ // Caller has already established that this feed is safe to serve
89
+ // (e.g. produced by intersectForSubscribe — the spec lifts its own
90
+ // authorization condition into the matches and therefore self-
91
+ // filters). Skip the redundant per-query distribution check, but
92
+ // keep tracking the returned references so /load stays consistent.
93
+ const factFeed = yield this.store.feed(specification, start, bookmark);
94
+ if (this.distributionEngine) {
95
+ const userReference = userIdentity
96
+ ? yield this.keystore.getUserFact(userIdentity)
97
+ : null;
98
+ const factReferences = factFeed.tuples
99
+ .flatMap(tuple => tuple.facts)
100
+ .filter((value, index, self) => self.findIndex((0, jinaga_1.factReferenceEquals)(value)) === index);
101
+ this.distributedFacts.add(factReferences, userReference);
76
102
  }
103
+ return factFeed;
77
104
  });
78
105
  }
79
106
  load(userIdentity, references) {
@@ -150,6 +177,27 @@ class AuthorizationKeystore {
150
177
  }
151
178
  });
152
179
  }
180
+ verifyDistributionOrIntersect(userIdentity, specification, namedStart) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ const start = specification.given.map(g => namedStart[g.label.name]);
183
+ if (!this.distributionEngine) {
184
+ return { type: "success", branches: [{ start, specification }] };
185
+ }
186
+ const userReference = userIdentity
187
+ ? yield this.keystore.getUserFact(userIdentity)
188
+ : null;
189
+ const targetFeeds = (0, jinaga_1.buildFeeds)(specification);
190
+ const canDistribute = yield this.distributionEngine.canDistributeToAll(targetFeeds, namedStart, userReference);
191
+ if (canDistribute.type === "success") {
192
+ return { type: "success", branches: [{ start, specification }] };
193
+ }
194
+ const result = yield this.distributionEngine.intersectForSubscribe(start, specification, userReference);
195
+ if (!result.intersected) {
196
+ return { type: "denied", reason: canDistribute.reason };
197
+ }
198
+ return { type: "success", branches: result.branches };
199
+ });
200
+ }
153
201
  }
154
202
  exports.AuthorizationKeystore = AuthorizationKeystore;
155
203
  //# sourceMappingURL=authorization-keystore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authorization-keystore.js","sourceRoot":"","sources":["../../src/authorization/authorization-keystore.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mCAiBgB;AAGhB,qEAAgE;AAEhE,MAAa,qBAAqB;IAK9B,YACY,WAAwB,EACxB,KAAc,EACd,QAAkB,EAC1B,kBAA6C,EAC7C,iBAA2C;QAJnC,gBAAW,GAAX,WAAW,CAAa;QACxB,UAAK,GAAL,KAAK,CAAS;QACd,aAAQ,GAAR,QAAQ,CAAU;QALtB,qBAAgB,GAAyB,IAAI,6CAAoB,EAAE,CAAC;QASxE,IAAI,CAAC,mBAAmB,GAAG,kBAAkB;YACzC,IAAI,4BAAmB,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,kBAAkB,GAAG,iBAAiB;YACvC,IAAI,2BAAkB,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;IACzD,CAAC;IAEK,mBAAmB,CAAC,YAA0B;;YAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;YACvE,MAAM,SAAS,GAAG;gBACA;oBACV,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,EAAE;iBACjB;aACJ,CAAC;YACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACjC,OAAO,QAAQ,CAAC;QACpB,CAAC;KAAA;IAEK,IAAI,CAAC,YAAiC,EAAE,KAAsB,EAAE,aAA4B;;YAC9F,IAAI,IAAI,CAAC,kBAAkB,EAAE;gBACzB,MAAM,aAAa,GAAyB,YAAY;oBACpD,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC;oBAC/C,CAAC,CAAC,IAAI,CAAC;gBACX,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,iCAC1D,GAAG,KACN,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,IAC9B,EAAE,EAAsB,CAAC,CAAC;gBAC5B,6DAA6D;gBAC7D,MAAM,KAAK,GAAG,IAAA,mBAAU,EAAC,aAAa,CAAC,CAAC;gBACxC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;gBACzG,IAAI,aAAa,CAAC,IAAI,KAAK,SAAS,EAAE;oBAClC,MAAM,IAAI,kBAAS,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;iBAC7C;aACJ;YACD,OAAO,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAC7D,CAAC;KAAA;IAEK,IAAI,CAAC,YAAiC,EAAE,aAA4B,EAAE,KAAsB,EAAE,QAAgB;;YAChH,IAAI,IAAI,CAAC,kBAAkB,EAAE;gBACzB,MAAM,aAAa,GAAyB,YAAY;oBACpD,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC;oBAC/C,CAAC,CAAC,IAAI,CAAC;gBACX,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,iCAC1D,GAAG,KACN,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,IAC9B,EAAE,EAAsB,CAAC,CAAC;gBAC5B,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;gBACnH,IAAI,aAAa,CAAC,IAAI,KAAK,SAAS,EAAE;oBAClC,MAAM,IAAI,kBAAS,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;iBAC7C;gBACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACvE,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM;qBACjC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC;qBAC7B,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAA,4BAAmB,EAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;gBAC1F,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;gBACzD,OAAO,QAAQ,CAAC;aACnB;iBACI;gBACD,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;aAChE;QACL,CAAC;KAAA;IAEK,IAAI,CAAC,YAA0B,EAAE,UAA2B;;YAC9D,IAAI,IAAI,CAAC,kBAAkB,EAAE;gBACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBACrF,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;gBAC9E,IAAI,CAAC,aAAa,EAAE;oBAChB,MAAM,IAAI,kBAAS,CAAC,cAAc,CAAC,CAAC;iBACvC;aACJ;YACD,OAAO,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,CAAC;KAAA;IAEK,IAAI,CAAC,YAAiC,EAAE,SAAyB;;YACnE,IAAI,IAAI,CAAC,mBAAmB,EAAE;gBAC1B,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBACrF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;gBACnF,MAAM,QAAQ,GAAc,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;oBACjF,CAAC,CAAC,CAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAE;oBAC/B,CAAC,CAAC,EAAE,CAAC;gBACT,MAAM,WAAW,GAAG,OAAO;qBACtB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;qBACtF,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBACtB,MAAM,WAAW,GAAG,CAAC,YAAY,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;oBACxD,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,WAAW,CAAC;oBAC1D,CAAC,CAAC,EAAE,CAAC;gBACT,MAAM,mBAAmB,GAAmB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;oBACxD,MAAM,MAAM,GAAG,IAAA,4BAAmB,EAAC,CAAC,CAAC,IAAI,CAAC,CAAC;oBAC3C,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;oBACrD,IAAI,CAAC,QAAQ,EAAE;wBACX,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;qBACnD;oBACD,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE;wBACxB,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;wBACzD,MAAM,cAAc,GAAG,UAAU;4BAC7B,CAAC,CAAC,UAAU,CAAC,UAAU;4BACvB,CAAC,CAAC,EAAE,CAAC;wBACT,OAAO;4BACH,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,UAAU,EAAE,QAAQ,CAAC,UAAU;iCAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;iCAClD,MAAM,CAAC,cAAc,CAAC;yBAC9B,CAAC;qBACL;yBACI,IAAI,CAAC,CAAC,OAAO,KAAK,UAAU,EAAE;wBAC/B,OAAO,QAAQ,CAAC;qBACnB;yBACI;wBACD,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;qBAC1C;gBACL,CAAC,CAAC,CAAC;gBACH,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;gBACxE,OAAO,cAAc,CAAC;aACzB;iBACI;gBACD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC9D,OAAO,cAAc,CAAC;aACzB;QACL,CAAC;KAAA;IAEK,kBAAkB,CAAC,YAAiC,EAAE,KAAsB,EAAE,UAA4B;;YAC5G,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBAC1B,OAAO;aACV;YAED,MAAM,aAAa,GAAyB,YAAY;gBACpD,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC;gBAC/C,CAAC,CAAC,IAAI,CAAC;YACX,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;YACzG,IAAI,aAAa,CAAC,IAAI,KAAK,SAAS,EAAE;gBAClC,MAAM,IAAI,kBAAS,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;aAC7C;QACL,CAAC;KAAA;CACJ;AAjJD,sDAiJC"}
1
+ {"version":3,"file":"authorization-keystore.js","sourceRoot":"","sources":["../../src/authorization/authorization-keystore.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { Handler } from "express";
2
2
  import { Authorization, FactManager, FeedCache, ProfileMessage } from "jinaga";
3
+ import { SubscriptionAuthorizer } from "../authorization/authorization-keystore";
3
4
  export interface RequestUser {
4
5
  provider: string;
5
6
  id: string;
@@ -11,7 +12,8 @@ export declare class HttpRouter {
11
12
  private feedCache;
12
13
  private allowedOrigin;
13
14
  handler: Handler;
14
- constructor(factManager: FactManager, authorization: Authorization, feedCache: FeedCache, allowedOrigin: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void));
15
+ private intersectedFeedOwners;
16
+ constructor(factManager: FactManager, authorization: Authorization & SubscriptionAuthorizer, feedCache: FeedCache, allowedOrigin: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void));
15
17
  private login;
16
18
  private load;
17
19
  private save;
@@ -24,6 +26,7 @@ export declare class HttpRouter {
24
26
  private readWithStreaming;
25
27
  private write;
26
28
  private feeds;
29
+ private queryFeed;
27
30
  private feed;
28
31
  private streamFeed;
29
32
  private streamAllInitialResults;
@@ -228,6 +228,9 @@ function postReadWithStreaming(method) {
228
228
  }
229
229
  };
230
230
  }
231
+ function subscriptionOwnerKey(userIdentity) {
232
+ return userIdentity ? `${userIdentity.provider}|${userIdentity.id}` : null;
233
+ }
231
234
  function serializeUserIdentity(user) {
232
235
  if (!user) {
233
236
  return null;
@@ -293,6 +296,15 @@ class HttpRouter {
293
296
  this.authorization = authorization;
294
297
  this.feedCache = feedCache;
295
298
  this.allowedOrigin = allowedOrigin;
299
+ // Map from feed hash → owning user key, for feeds the server cleared
300
+ // via intersection. The cached spec self-filters by the lifted auth
301
+ // condition (the requesting user's fact is baked into start), so it
302
+ // is safe to skip the per-query distribution check — but ONLY for
303
+ // the same user who originally requested the subscription. Another
304
+ // authenticated user who somehow obtained the hash must go through
305
+ // the normal authorization.feed path, which will deny their request
306
+ // since the intersected spec is not authorizable in its own right.
307
+ this.intersectedFeedOwners = new Map();
296
308
  const router = (0, express_1.Router)();
297
309
  const applyAllowOrigin = this.applyAllowOrigin.bind(this);
298
310
  router.get('/login', applyAllowOrigin, getAuthenticate(user => this.login(user)));
@@ -436,16 +448,81 @@ class HttpRouter {
436
448
  throw new jinaga_1.Invalid(failures.join("\n"));
437
449
  }
438
450
  const namedStart = specification.given.reduce((map, g, index) => (Object.assign(Object.assign({}, map), { [g.label.name]: start[index] })), {});
439
- // Verify that I can distribute all feeds to the user.
440
- const feeds = (0, jinaga_1.buildFeeds)(specification);
451
+ // Resolve distribution. When the user is not authorized for the
452
+ // original spec but the distribution rules can rewrite it via
453
+ // intersection (jinaga.js#130), accept the subscription with
454
+ // empty results and let the inverse engine activate it later.
455
+ //
456
+ // Even when no rule applies at all, accept the subscription:
457
+ // the per-query distribution check inside streamFeed /
458
+ // feed returns "denied" and keeps the stream alive so the
459
+ // client can be notified the moment the authorizing fact
460
+ // arrives. Failing /feeds here would force the client to
461
+ // re-subscribe on a poll loop.
441
462
  const userIdentity = serializeUserIdentity(user);
442
- yield this.authorization.verifyDistribution(userIdentity, feeds, namedStart);
443
- const feedHashes = this.feedCache.addFeeds(feeds, namedStart);
463
+ const intersectResult = yield this.authorization.verifyDistributionOrIntersect(userIdentity, specification, namedStart);
464
+ let branches;
465
+ let intersected;
466
+ if (intersectResult.type === "denied") {
467
+ jinaga_1.Trace.warn(`/feeds accepting spec without applicable distribution rule: ${intersectResult.reason}`);
468
+ branches = [{ start, specification }];
469
+ intersected = false;
470
+ }
471
+ else {
472
+ branches = intersectResult.branches;
473
+ intersected = branches.length !== 1 || branches[0].specification !== specification;
474
+ }
475
+ const ownerKey = subscriptionOwnerKey(userIdentity);
476
+ const feedHashes = [];
477
+ for (const branch of branches) {
478
+ const branchNamedStart = branch.specification.given.reduce((map, g, index) => (Object.assign(Object.assign({}, map), { [g.label.name]: branch.start[index] })), {});
479
+ const branchFeeds = (0, jinaga_1.buildFeeds)(branch.specification);
480
+ const branchHashes = this.feedCache.addFeeds(branchFeeds, branchNamedStart);
481
+ feedHashes.push(...branchHashes);
482
+ if (intersected) {
483
+ for (const hash of branchHashes) {
484
+ // Bind the hash to its owner so a different
485
+ // authenticated user who obtains it cannot reuse
486
+ // the cached spec — which carries the original
487
+ // owner's user fact in its start — to fetch
488
+ // facts authorized for that owner.
489
+ //
490
+ // The map allows only one owner per hash, which
491
+ // relies on the invariant that intersected specs
492
+ // bind the requesting user's fact into start —
493
+ // making the hash user-specific. If two distinct
494
+ // owners ever collide on the same hash, that
495
+ // invariant has broken in intersectForSubscribe;
496
+ // warn so we notice instead of silently
497
+ // overwriting.
498
+ const existing = this.intersectedFeedOwners.get(hash);
499
+ if (existing !== undefined && existing !== ownerKey) {
500
+ jinaga_1.Trace.warn(`Intersected feed hash ${hash} re-bound from owner ${existing} to ${ownerKey}; intersection invariant may be broken`);
501
+ }
502
+ this.intersectedFeedOwners.set(hash, ownerKey);
503
+ }
504
+ }
505
+ }
444
506
  return {
445
507
  feeds: feedHashes
446
508
  };
447
509
  }));
448
510
  }
511
+ queryFeed(feedHash, userIdentity, specification, start, bookmark) {
512
+ return __awaiter(this, void 0, void 0, function* () {
513
+ const cachedOwner = this.intersectedFeedOwners.get(feedHash);
514
+ const requesterOwner = subscriptionOwnerKey(userIdentity);
515
+ if (cachedOwner !== undefined && cachedOwner === requesterOwner) {
516
+ const feed = yield this.authorization.feedPreVerified(userIdentity, specification, start, bookmark);
517
+ return { type: "success", feed };
518
+ }
519
+ // For everyone else (different user, anonymous mismatch, or a
520
+ // non-intersected hash) go through the normal distribution-checked
521
+ // path. A "denied" result means the streaming / polling caller
522
+ // serves an empty page and keeps the subscription alive.
523
+ return yield this.authorization.feedWithDistribution(userIdentity, specification, start, bookmark);
524
+ });
525
+ }
449
526
  feed(user, params, query) {
450
527
  return jinaga_1.Trace.dependency("feed", params["hash"], () => __awaiter(this, void 0, void 0, function* () {
451
528
  var _a;
@@ -460,12 +537,19 @@ class HttpRouter {
460
537
  const bookmark = (_a = query["b"]) !== null && _a !== void 0 ? _a : "";
461
538
  const userIdentity = serializeUserIdentity(user);
462
539
  const start = feedDefinition.feed.given.map(g => feedDefinition.namedStart[g.label.name]);
463
- const results = yield this.authorization.feed(userIdentity, feedDefinition.feed, start, bookmark);
540
+ const result = yield this.queryFeed(feedHash, userIdentity, feedDefinition.feed, start, bookmark);
541
+ if (result.type === "denied") {
542
+ // The subscription has been accepted; treat poll-time
543
+ // distribution failures as an empty page so the client
544
+ // can keep polling and pick up results when an
545
+ // authorizing fact arrives.
546
+ return { references: [], bookmark };
547
+ }
464
548
  // Return distinct fact references from all the tuples.
465
- const references = results.tuples.flatMap(t => t.facts).filter((value, index, self) => self.findIndex(f => f.hash === value.hash && f.type === value.type) === index);
549
+ const references = result.feed.tuples.flatMap(t => t.facts).filter((value, index, self) => self.findIndex(f => f.hash === value.hash && f.type === value.type) === index);
466
550
  const response = {
467
551
  references,
468
- bookmark: results.bookmark
552
+ bookmark: result.feed.bookmark
469
553
  };
470
554
  return response;
471
555
  }));
@@ -497,7 +581,7 @@ class HttpRouter {
497
581
  // Continuous initial query until exhausted
498
582
  console.log(`[StreamFeed:${connectionId}] Starting initial data streaming...`);
499
583
  const initialStart = Date.now();
500
- bookmark = yield this.streamAllInitialResults(userIdentity, feedDefinition, start, bookmark, stream);
584
+ bookmark = yield this.streamAllInitialResults(feedHash, userIdentity, feedDefinition, start, bookmark, stream);
501
585
  const initialDuration = Date.now() - initialStart;
502
586
  console.log(`[StreamFeed:${connectionId}] Initial data streaming complete - Duration: ${initialDuration}ms, Final bookmark: ${bookmark || 'empty'}`);
503
587
  // Set up real-time listeners after initial data is complete
@@ -516,7 +600,7 @@ class HttpRouter {
516
600
  if (matchingResults.length != 0) {
517
601
  console.log(`[StreamFeed:${connectionId}] Processing matching results...`);
518
602
  const responseStart = Date.now();
519
- bookmark = yield this.streamFeedResponse(userIdentity, feedDefinition, start, bookmark, stream, true);
603
+ bookmark = yield this.streamFeedResponse(feedHash, userIdentity, feedDefinition, start, bookmark, stream, true);
520
604
  const responseDuration = Date.now() - responseStart;
521
605
  console.log(`[StreamFeed:${connectionId}] Feed response sent - Duration: ${responseDuration}ms, New bookmark: ${bookmark || 'empty'}`);
522
606
  }
@@ -534,9 +618,42 @@ class HttpRouter {
534
618
  }));
535
619
  });
536
620
  console.log(`[StreamFeed:${connectionId}] All listeners registered - Count: ${listeners.length}`);
621
+ // Anchor listeners (jinaga.js#129): the inverse-listener path
622
+ // above only fires when a descendant of the given arrives. If
623
+ // the given fact itself isn't yet in the store when the client
624
+ // subscribes, neither the initial query nor any later inverse
625
+ // can deliver results until the given is recorded — and the
626
+ // given fact has no descendants whose save would re-trigger the
627
+ // inverse with the matching givenHash. Register a listener per
628
+ // unique anchor that re-runs streamFeedResponse the moment a
629
+ // saved fact matches the anchor's (type, hash).
630
+ const uniqueAnchors = start.filter((s, i, arr) => arr.findIndex(other => other.type === s.type && other.hash === s.hash) === i);
631
+ console.log(`[StreamFeed:${connectionId}] Registering ${uniqueAnchors.length} anchor listener(s)`);
632
+ const anchorListeners = uniqueAnchors.map(anchor => {
633
+ const anchorSpec = {
634
+ given: [{ label: { name: "x", type: anchor.type }, conditions: [] }],
635
+ matches: [],
636
+ projection: { type: "fact", label: "x" }
637
+ };
638
+ return this.factManager.addSpecificationListener(anchorSpec, (results) => __awaiter(this, void 0, void 0, function* () {
639
+ try {
640
+ const matched = results.some(pr => {
641
+ const ref = pr.tuple && pr.tuple.x;
642
+ return ref && ref.type === anchor.type && ref.hash === anchor.hash;
643
+ });
644
+ if (matched) {
645
+ console.log(`[StreamFeed:${connectionId}] Anchor fact arrived - Type: ${anchor.type}, Hash: ${anchor.hash.substring(0, 8)}...`);
646
+ bookmark = yield this.streamFeedResponse(feedHash, userIdentity, feedDefinition, start, bookmark, stream, true);
647
+ }
648
+ }
649
+ catch (error) {
650
+ console.error(`[StreamFeed:${connectionId}] ERROR in anchor listener: ${error}`);
651
+ }
652
+ }));
653
+ });
537
654
  stream.done(() => {
538
655
  const cleanupStart = Date.now();
539
- console.log(`[StreamFeed:${connectionId}] CLEANUP STARTED - Removing ${listeners.length} listeners`);
656
+ console.log(`[StreamFeed:${connectionId}] CLEANUP STARTED - Removing ${listeners.length + anchorListeners.length} listener(s)`);
540
657
  for (let i = 0; i < listeners.length; i++) {
541
658
  try {
542
659
  this.factManager.removeSpecificationListener(listeners[i]);
@@ -546,6 +663,14 @@ class HttpRouter {
546
663
  console.error(`[StreamFeed:${connectionId}] ERROR removing listener ${i + 1}: ${error}`);
547
664
  }
548
665
  }
666
+ for (let i = 0; i < anchorListeners.length; i++) {
667
+ try {
668
+ this.factManager.removeSpecificationListener(anchorListeners[i]);
669
+ }
670
+ catch (error) {
671
+ console.error(`[StreamFeed:${connectionId}] ERROR removing anchor listener ${i + 1}: ${error}`);
672
+ }
673
+ }
549
674
  const cleanupDuration = Date.now() - cleanupStart;
550
675
  const totalDuration = Date.now() - startTime;
551
676
  console.log(`[StreamFeed:${connectionId}] CLEANUP COMPLETE - Cleanup duration: ${cleanupDuration}ms, Total connection duration: ${totalDuration}ms`);
@@ -560,7 +685,7 @@ class HttpRouter {
560
685
  }
561
686
  });
562
687
  }
563
- streamAllInitialResults(userIdentity, feedDefinition, start, initialBookmark, stream) {
688
+ streamAllInitialResults(feedHash, userIdentity, feedDefinition, start, initialBookmark, stream) {
564
689
  return __awaiter(this, void 0, void 0, function* () {
565
690
  const streamId = Math.random().toString(36).substring(2, 8);
566
691
  console.log(`[InitialResults:${streamId}] Starting initial data fetch - Bookmark: ${initialBookmark || 'empty'}`);
@@ -573,7 +698,17 @@ class HttpRouter {
573
698
  while (hasMoreResults && pageCount < maxPages) {
574
699
  const pageStart = Date.now();
575
700
  console.log(`[InitialResults:${streamId}] Fetching page ${pageCount + 1} - Bookmark: ${bookmark || 'empty'}`);
576
- const results = yield this.authorization.feed(userIdentity, feedDefinition.feed, start, bookmark);
701
+ const queryResult = yield this.queryFeed(feedHash, userIdentity, feedDefinition.feed, start, bookmark);
702
+ if (queryResult.type === "denied") {
703
+ // The subscription has already been accepted; tearing
704
+ // down the stream on a distribution failure would force
705
+ // the client to re-subscribe just to keep waiting.
706
+ // Treat as empty and let the listener path re-evaluate
707
+ // when an authorizing fact arrives.
708
+ console.log(`[InitialResults:${streamId}] Distribution failed (${queryResult.reason}) - serving empty and keeping stream live`);
709
+ return bookmark;
710
+ }
711
+ const results = queryResult.feed;
577
712
  const fetchDuration = Date.now() - pageStart;
578
713
  console.log(`[InitialResults:${streamId}] Page ${pageCount + 1} fetched - Tuples: ${results.tuples.length}, Duration: ${fetchDuration}ms`);
579
714
  // Check if we got results
@@ -627,13 +762,21 @@ class HttpRouter {
627
762
  return bookmark;
628
763
  });
629
764
  }
630
- streamFeedResponse(userIdentity, feedDefinition, start, bookmark, stream, skipIfEmpty = false) {
765
+ streamFeedResponse(feedHash, userIdentity, feedDefinition, start, bookmark, stream, skipIfEmpty = false) {
631
766
  return __awaiter(this, void 0, void 0, function* () {
632
767
  const responseId = Math.random().toString(36).substring(2, 6);
633
768
  const startTime = Date.now();
634
769
  console.log(`[FeedResponse:${responseId}] Starting feed response - Bookmark: ${bookmark || 'empty'}, Skip if empty: ${skipIfEmpty}`);
635
770
  const feedStart = Date.now();
636
- const results = yield this.authorization.feed(userIdentity, feedDefinition.feed, start, bookmark);
771
+ const queryResult = yield this.queryFeed(feedHash, userIdentity, feedDefinition.feed, start, bookmark);
772
+ if (queryResult.type === "denied") {
773
+ // The subscription stays live across distribution failures
774
+ // so the inverse / anchor listeners can deliver results
775
+ // once the authorizing fact arrives.
776
+ console.log(`[FeedResponse:${responseId}] Distribution failed (${queryResult.reason}) - keeping stream live`);
777
+ return bookmark;
778
+ }
779
+ const results = queryResult.feed;
637
780
  const feedDuration = Date.now() - feedStart;
638
781
  console.log(`[FeedResponse:${responseId}] Authorization feed complete - Tuples: ${results.tuples.length}, Duration: ${feedDuration}ms`);
639
782
  // Return distinct fact references from all the tuples.