create-t2k 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ a Decision Context, two executable policies, and disjoint replay evidence.
5
5
  The existing decision-loop project remains the default profile.
6
6
 
7
7
  ```bash
8
- npx create-t2k@latest my-decision-loop
8
+ npx create-t2k@0.4.2 my-decision-loop
9
9
  cd my-decision-loop
10
10
  npm start
11
11
  ```
@@ -31,6 +31,10 @@ Stop the local containers without deleting lifecycle data with `npm run
31
31
  db:down`. Use the explicitly destructive `npm run db:reset` only when you
32
32
  intend to delete the disposable local database volume.
33
33
 
34
+ Compose binds PostgreSQL to `127.0.0.1:55432` only. The included `t2k` username
35
+ and `t2k` password are disposable local-only quickstart credentials; never
36
+ reuse them or expose this generated database to another host.
37
+
34
38
  To expose ontology validation, compilation, policy execution, replay, and
35
39
  reward evaluation to an MCP host, add:
36
40
 
@@ -39,7 +43,7 @@ reward evaluation to an MCP host, add:
39
43
  "mcpServers": {
40
44
  "t2k": {
41
45
  "command": "npx",
42
- "args": ["-y", "@t2kai/mcp@latest"]
46
+ "args": ["-y", "@t2kai/mcp@0.3.0"]
43
47
  }
44
48
  }
45
49
  }
@@ -52,9 +56,12 @@ before enabling database access or agent writes.
52
56
  Use `--no-install` to generate files without running `npm install`:
53
57
 
54
58
  ```bash
55
- npx create-t2k@latest my-decision-loop --no-install
59
+ npx create-t2k@0.4.2 my-decision-loop --no-install
56
60
  ```
57
61
 
62
+ The normal command installs dependencies for you. Run `npm install` inside the
63
+ generated project only when you chose `--no-install`.
64
+
58
65
  The command refuses to write into a non-empty directory. Node.js 20.10 or newer
59
66
  is required.
60
67
 
@@ -63,7 +70,7 @@ is required.
63
70
  Generate a focused, fully synthetic source-integration project explicitly:
64
71
 
65
72
  ```bash
66
- npx create-t2k@latest my-integration-hub --profile integration-hub
73
+ npx create-t2k@0.4.2 my-integration-hub --profile integration-hub
67
74
  cd my-integration-hub
68
75
  npm start
69
76
  ```
@@ -71,21 +78,31 @@ npm start
71
78
  This profile maps two independent registry records into one canonical party
72
79
  identity, preserves conflicting name evidence, and applies a versioned authority
73
80
  order to a conflicting registry state. It runs reconciliation again with the
74
- opposite input order and proves the proposal hash is identical.
81
+ opposite input order and reports that specifically scoped proposal-hash
82
+ comparison; it does not claim to check every permutation after more sources are
83
+ added.
75
84
 
76
85
  Every regular `source-records/*.json` file is discovered in canonical filename
77
86
  order, so the generated README experiments work without editing the runner:
78
- reverse the versioned authority priorities or add a third synthetic source with
79
- the same canonical key and run `npm start` again.
87
+ reverse the authority priorities while bumping `policyVersion`, or add a third
88
+ synthetic source with the same canonical key and run `npm start` again.
80
89
 
81
90
  The output is a deterministic evidence packet for human review. It preserves
82
- the complete source receipts and alternatives inside the hashed packet, does
83
- not mutate any source record, and does not promote the authority-selected
91
+ every canonical record with its complete receipt, the exact loaded
92
+ `@t2kai/core` package version, the complete authority policy, and the full
93
+ forward and reverse reconciliation proposals and issues inside the hashed
94
+ packet. It does not mutate any source record or promote the authority-selected
84
95
  candidate to accepted truth. Both included synthetic sources deliberately have
85
96
  an `unknown` authentication state. The unkeyed hashes demonstrate deterministic
86
97
  self-consistency only; they are not signatures, authentication, or proof that a
87
98
  source assertion is true.
88
99
 
100
+ After an envelope has been mapped, keep it immutable: represent a later
101
+ observation with a new file, source-record key, payload record ID, and event and
102
+ observation times. Bump `policyVersion` after changing authority priorities;
103
+ bump `mappingVersion` and `ontologyVersion` after changing a mapping; and bump
104
+ `ontologyVersion` for any other ontology contract change.
105
+
89
106
  Supported profiles are `decision-loop` (the default) and `integration-hub`.
90
107
  An unknown or repeated `--profile` option fails before the target directory is
91
108
  created.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-t2k",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Create a local T2K governed-decision project in minutes.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -4,10 +4,12 @@ This project is a local T2K governed-decision quickstart. Its data is fully
4
4
  synthetic.
5
5
 
6
6
  ```bash
7
- npm install
8
7
  npm start
9
8
  ```
10
9
 
10
+ `create-t2k` installs dependencies by default. Run `npm install` first only if
11
+ this project was generated with `--no-install`.
12
+
11
13
  The run performs five explicit steps:
12
14
 
13
15
  1. validate `ontology-pack.json` against the published T2K schema;
@@ -44,6 +46,10 @@ the local containers while preserving their volume, run `npm run db:down`. To
44
46
  explicitly delete the disposable local volume and all lifecycle data in it, run
45
47
  `npm run db:reset`.
46
48
 
49
+ Compose binds PostgreSQL to `127.0.0.1:55432` only. Its `t2k` username and
50
+ `t2k` password are disposable local-only quickstart credentials; never reuse
51
+ them outside this generated project or expose this database to another host.
52
+
47
53
  ## Connect an MCP host
48
54
 
49
55
  Expose validation, compilation, policy execution, replay, and reward evaluation
@@ -54,7 +60,7 @@ to an MCP host with the safe database-free mode:
54
60
  "mcpServers": {
55
61
  "t2k": {
56
62
  "command": "npx",
57
- "args": ["-y", "@t2kai/mcp@latest"]
63
+ "args": ["-y", "@t2kai/mcp@0.3.0"]
58
64
  }
59
65
  }
60
66
  }
@@ -67,9 +73,15 @@ mutation modes.
67
73
  ## Change the example
68
74
 
69
75
  - Change current facts in `decision-context.json`.
70
- - Change executable rules in `policies/*.json`.
76
+ - Change executable rules in `policies/*.json` and bump the policy `version`.
71
77
  - Add observed outcomes to `episodes/holdout.json` without reusing training data.
72
- - Change concepts and decision contracts in `ontology-pack.json`.
78
+ - Change concepts or decision contracts in `ontology-pack.json` and bump
79
+ `ontologyVersion`.
80
+
81
+ Treat any context, policy, episode, or ontology artifact that has already been
82
+ executed or persisted as immutable evidence. Model a later observation with a
83
+ new context or episode identifier, and keep the prior artifact available for
84
+ replay instead of rewriting history.
73
85
 
74
86
  The quickstart uses local files, local PostgreSQL, and `@t2kai/core`; it does not
75
87
  send data to a hosted service. Set `T2K_DATABASE_URL` to use another Postgres
@@ -2,6 +2,8 @@ services:
2
2
  postgres:
3
3
  image: postgres:16-alpine
4
4
  environment:
5
+ # Disposable local-only quickstart credentials. Never reuse t2k/t2k
6
+ # outside this generated project.
5
7
  POSTGRES_DB: t2k_reference
6
8
  POSTGRES_PASSWORD: t2k
7
9
  POSTGRES_USER: t2k
@@ -11,7 +13,7 @@ services:
11
13
  timeout: 5s
12
14
  retries: 15
13
15
  ports:
14
- - "55432:5432"
16
+ - "127.0.0.1:55432:5432"
15
17
  volumes:
16
18
  - t2k-reference-data:/var/lib/postgresql/data
17
19
 
@@ -15,6 +15,6 @@
15
15
  "lifecycle": "node src/lifecycle.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@t2kai/core": "^0.4.0"
18
+ "@t2kai/core": "^0.4.2"
19
19
  }
20
20
  }
@@ -10,8 +10,21 @@ import { compileOntologyPackSet } from "@t2kai/core/compiler";
10
10
  import { PostgresReferenceLifecycle } from "@t2kai/core/postgres";
11
11
 
12
12
  const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
- const readJson = async (relativePath) =>
14
- JSON.parse(await fs.readFile(path.join(projectRoot, relativePath), "utf8"));
13
+
14
+ async function readJson(relativePath) {
15
+ const contents = await fs.readFile(
16
+ path.join(projectRoot, relativePath),
17
+ "utf8"
18
+ );
19
+ try {
20
+ return JSON.parse(contents);
21
+ } catch (error) {
22
+ const detail = error instanceof Error ? `: ${error.message}` : "";
23
+ throw new SyntaxError(`Invalid JSON in ${relativePath}${detail}`, {
24
+ cause: error,
25
+ });
26
+ }
27
+ }
15
28
  const [manifest, baseline, candidatePolicy, holdout] = await Promise.all([
16
29
  readJson("ontology-pack.json"),
17
30
  readJson("policies/baseline.json"),
@@ -12,8 +12,21 @@ import {
12
12
  import { compileOntologyPackSet } from "@t2kai/core/compiler";
13
13
 
14
14
  const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
- const readJson = async (relativePath) =>
16
- JSON.parse(await fs.readFile(path.join(projectRoot, relativePath), "utf8"));
15
+
16
+ async function readJson(relativePath) {
17
+ const contents = await fs.readFile(
18
+ path.join(projectRoot, relativePath),
19
+ "utf8"
20
+ );
21
+ try {
22
+ return JSON.parse(contents);
23
+ } catch (error) {
24
+ const detail = error instanceof Error ? `: ${error.message}` : "";
25
+ throw new SyntaxError(`Invalid JSON in ${relativePath}${detail}`, {
26
+ cause: error,
27
+ });
28
+ }
29
+ }
17
30
 
18
31
  const [manifest, context, baseline, candidate, episodes] = await Promise.all([
19
32
  readJson("ontology-pack.json"),
@@ -5,10 +5,12 @@ two independently governed source records can be mapped into one canonical
5
5
  identity without silently merging evidence or declaring either source true.
6
6
 
7
7
  ```bash
8
- npm install
9
8
  npm start
10
9
  ```
11
10
 
11
+ `create-t2k` installs dependencies by default. Run `npm install` first only if
12
+ this project was generated with `--no-install`.
13
+
12
14
  The run performs these explicit steps:
13
15
 
14
16
  1. validate and compile `ontology-pack.json`;
@@ -17,8 +19,9 @@ The run performs these explicit steps:
17
19
  3. preserve all conflicting display-name candidates;
18
20
  4. apply `authority-policy.json` to select a registry-state candidate within
19
21
  the proposal;
20
- 5. repeat reconciliation in the opposite input order; and
21
- 6. emit a deterministic evidence packet for human review.
22
+ 5. compare the forward source order with its reverse (not every possible
23
+ ordering when more than two sources exist); and
24
+ 6. emit a repeatable evidence packet for human review.
22
25
 
23
26
  The selected registry-state value is only a candidate within a non-mutating
24
27
  proposal. No source record is changed and no value becomes accepted truth.
@@ -26,18 +29,29 @@ Both source envelopes deliberately use `authenticationState: "unknown"`.
26
29
  Production callers must authenticate or attest sources and policies outside
27
30
  this example.
28
31
 
29
- Each complete source-mapping receipt is included in the evidence packet before
30
- the packet hash is computed. The receipt, policy, proposal, and packet hashes
31
- are unkeyed deterministic self-consistency checks. They are not signatures,
32
- authentication tokens, or proof that an assertion is true.
33
-
34
- ## Explore the hub
35
-
36
- - Change a source value in `source-records/` and inspect the preserved evidence.
37
- - Reverse the priorities in `authority-policy.json`, rerun, and inspect the
38
- changed proposed authority selection.
39
- - Add a third synthetic `.json` source with the same `party_key`; it is
40
- discovered automatically on the next run.
32
+ Each canonical record is paired with its complete source-mapping receipt in the
33
+ evidence packet. The packet also includes the exact loaded `@t2kai/core`
34
+ package version, the complete authority policy, both complete reconciliation
35
+ proposals and their issues, and an explicitly scoped forward-versus-reverse
36
+ hash comparison. All of those fields are included before the packet hash is
37
+ computed. The receipt, policy, proposal, and packet hashes are unkeyed
38
+ deterministic self-consistency checks. They are not signatures, authentication
39
+ tokens, or proof that an assertion is true.
40
+
41
+ ## Explore the hub safely
42
+
43
+ - Treat a source envelope as immutable once it has been mapped. To model a
44
+ later observation, copy it to a new `.json` file and assign a new
45
+ `sourceRecordKey`, payload `record_id`, `eventTime`, and `observedTime`; never
46
+ rewrite the earlier envelope.
47
+ - To reverse the priorities in `authority-policy.json`, also bump
48
+ `policyVersion`, rerun, and inspect the changed proposed authority selection.
49
+ - Add a third synthetic `.json` source with the same `party_key` and its own
50
+ new envelope identifiers and timestamps; it is discovered automatically on
51
+ the next run.
52
+ - When a field mapping changes, bump its `mappingVersion`. Because mappings are
53
+ part of this ontology pack, also bump `ontologyVersion`; bump
54
+ `ontologyVersion` for any other ontology contract change as well.
41
55
  - Replace `humanCheckpoint: "always"` only after defining a real review and
42
56
  authorization boundary in the calling system.
43
57
 
@@ -11,6 +11,6 @@
11
11
  "check": "node src/run.mjs"
12
12
  },
13
13
  "dependencies": {
14
- "@t2kai/core": "^0.4.0"
14
+ "@t2kai/core": "^0.4.2"
15
15
  }
16
16
  }
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import fs from "node:fs/promises";
3
+ import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
 
@@ -15,9 +16,18 @@ import {
15
16
  } from "@t2kai/core/compiler";
16
17
 
17
18
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
19
+ const loadPackageManifest = createRequire(import.meta.url);
18
20
 
19
21
  async function readJson(relativePath) {
20
- return JSON.parse(await fs.readFile(path.join(root, relativePath), "utf8"));
22
+ const contents = await fs.readFile(path.join(root, relativePath), "utf8");
23
+ try {
24
+ return JSON.parse(contents);
25
+ } catch (error) {
26
+ const detail = error instanceof Error ? `: ${error.message}` : "";
27
+ throw new SyntaxError(`Invalid JSON in ${relativePath}${detail}`, {
28
+ cause: error,
29
+ });
30
+ }
21
31
  }
22
32
 
23
33
  async function readSourceRecords() {
@@ -43,6 +53,17 @@ const manifest = await readJson("ontology-pack.json");
43
53
  const authorityPolicy = await readJson("authority-policy.json");
44
54
  const sourceRecords = await readSourceRecords();
45
55
  const sourceSnapshot = structuredClone(sourceRecords);
56
+ const corePackageManifest = loadPackageManifest("@t2kai/core/package.json");
57
+ assert.equal(
58
+ corePackageManifest.name,
59
+ "@t2kai/core",
60
+ "The integration packet must identify the loaded core runtime."
61
+ );
62
+ assert.ok(
63
+ typeof corePackageManifest.version === "string" &&
64
+ corePackageManifest.version.length > 0,
65
+ "The loaded core runtime must expose an exact package version."
66
+ );
46
67
 
47
68
  const validation = validateOntologyPackManifest(manifest);
48
69
  assert.equal(
@@ -160,8 +181,13 @@ assert.ok(preferAuthorityField.candidates.length >= 2);
160
181
 
161
182
  const packetWithoutHash = {
162
183
  profile: "integration-hub",
184
+ coreRuntime: {
185
+ packageName: corePackageManifest.name,
186
+ packageVersion: corePackageManifest.version,
187
+ },
163
188
  ontology: `${manifest.ontologyId}@${manifest.ontologyVersion}`,
164
189
  resolutionHash: compilation.resolutionHash,
190
+ authorityPolicy: structuredClone(authorityPolicy),
165
191
  sourceEvidence: mappedResults
166
192
  .map((result) => ({
167
193
  summary: {
@@ -172,6 +198,7 @@ const packetWithoutHash = {
172
198
  receiptHash: result.receipt.receiptHash,
173
199
  humanReviewRequired: result.receipt.humanReviewRequired,
174
200
  },
201
+ canonicalRecord: result.canonicalRecord,
175
202
  receipt: result.receipt,
176
203
  }))
177
204
  .sort((left, right) =>
@@ -181,38 +208,27 @@ const packetWithoutHash = {
181
208
  )
182
209
  ),
183
210
  reconciliation: {
184
- proposalHash: reconciliation.proposalHash,
185
- status: reconciliation.status,
186
- canonicalIdentity: reconciliation.identity,
187
- authorityPolicy: {
188
- policyId: reconciliation.policyId,
189
- policyVersion: reconciliation.policyVersion,
190
- policyHash: reconciliation.policyHash,
191
- },
192
- deterministicAcrossInputOrder:
193
- reconciliation.proposalHash === reverseOrderReconciliation.proposalHash,
194
- humanReviewRequired: reconciliation.humanReviewRequired,
195
- nonMutating: reconciliation.nonMutating,
196
- alternativesPreserved: reconciliation.alternativesPreserved,
197
- preserveAll: {
198
- propertyRef: preserveAllField.propertyRef,
199
- resolution: preserveAllField.resolution,
200
- selectedValue: preserveAllField.selectedValue,
201
- candidates: preserveAllField.candidates,
202
- },
203
- preferAuthority: {
204
- propertyRef: preferAuthorityField.propertyRef,
205
- resolution: preferAuthorityField.resolution,
206
- selectedWithinProposal: preferAuthorityField.selectedValue,
207
- candidates: preferAuthorityField.candidates,
211
+ proposal: reconciliation,
212
+ reverseInputOrderProposal: reverseOrderReconciliation,
213
+ forwardReverseInputOrderCheck: {
214
+ comparisonScope: "forward_and_reverse_input_order_only",
215
+ allPermutationsChecked: mappedResults.length === 2,
216
+ forwardReceiptHashes: mappedResults.map(
217
+ (result) => result.receipt.receiptHash
218
+ ),
219
+ reverseReceiptHashes: [...mappedResults]
220
+ .reverse()
221
+ .map((result) => result.receipt.receiptHash),
222
+ forwardProposalHash: reconciliation.proposalHash,
223
+ reverseProposalHash: reverseOrderReconciliation.proposalHash,
224
+ proposalHashesMatch:
225
+ reconciliation.proposalHash === reverseOrderReconciliation.proposalHash,
208
226
  },
209
227
  },
210
228
  humanReview: {
211
229
  status: "pending_human_review",
212
230
  proposalOnly: true,
213
- issues: reconciliation.issues
214
- .filter((issue) => issue.severity === "review")
215
- .map(({ code, message, receiptHash }) => ({ code, message, receiptHash })),
231
+ issues: reconciliation.issues,
216
232
  },
217
233
  boundaries: {
218
234
  syntheticDataOnly: true,