create-t2k 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/LICENSE +17 -0
- package/README.md +26 -0
- package/bin/create-t2k.mjs +47 -0
- package/package.json +45 -0
- package/src/scaffold.mjs +175 -0
- package/template/.gitignore.template +3 -0
- package/template/README.md.template +31 -0
- package/template/decision-context.json +31 -0
- package/template/episodes/holdout.json +322 -0
- package/template/ontology-pack.json +129 -0
- package/template/package.json.template +16 -0
- package/template/policies/baseline.json +27 -0
- package/template/policies/candidate.json +28 -0
- package/template/src/run.mjs +109 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 T2K contributors
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# `create-t2k`
|
|
2
|
+
|
|
3
|
+
Create a runnable local T2K project with a synthetic ontology, accepted facts,
|
|
4
|
+
a Decision Context, two executable policies, and disjoint replay evidence.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx create-t2k my-decision-loop
|
|
8
|
+
cd my-decision-loop
|
|
9
|
+
npm start
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The generated run validates and compiles the ontology pack, executes the
|
|
13
|
+
baseline and challenger policies against the current facts, computes a held-out
|
|
14
|
+
replay comparison, and emits a recommendation that still requires explicit
|
|
15
|
+
human authorization.
|
|
16
|
+
|
|
17
|
+
Use `--no-install` to generate files without running `npm install`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx create-t2k my-decision-loop --no-install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The command refuses to write into a non-empty directory. Node.js 20.10 or newer
|
|
24
|
+
is required.
|
|
25
|
+
|
|
26
|
+
Apache-2.0. Contributions require DCO sign-off in the public repository.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
import { parseArguments, scaffoldProject } from "../src/scaffold.mjs";
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const packageManifest = JSON.parse(
|
|
12
|
+
await fs.readFile(path.join(packageRoot, "package.json"), "utf8")
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const help = `Create a local T2K governed-decision project.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
create-t2k [directory] [options]
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--no-install Generate the project without installing dependencies
|
|
22
|
+
--yes Accept non-interactive defaults
|
|
23
|
+
-h, --help Show this help
|
|
24
|
+
-v, --version Show the package version
|
|
25
|
+
|
|
26
|
+
The default directory is my-t2k-project. Existing non-empty directories are
|
|
27
|
+
never overwritten.`;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const options = parseArguments(process.argv.slice(2));
|
|
31
|
+
if (options.help) {
|
|
32
|
+
process.stdout.write(`${help}\n`);
|
|
33
|
+
} else if (options.version) {
|
|
34
|
+
process.stdout.write(`${packageManifest.version}\n`);
|
|
35
|
+
} else {
|
|
36
|
+
await scaffoldProject({
|
|
37
|
+
targetDirectory: options.targetDirectory,
|
|
38
|
+
install: options.install,
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
stdout: process.stdout,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
process.stderr.write(`create-t2k: ${message}\n`);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-t2k",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a local T2K governed-decision project in minutes.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"ontology",
|
|
8
|
+
"decision-intelligence",
|
|
9
|
+
"ai-agents",
|
|
10
|
+
"scaffolding"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://t2k.ai/developers/",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/sigaihealth/t2k-core.git",
|
|
16
|
+
"directory": "packages/create-t2k"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/sigaihealth/t2k-core/issues"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"create-t2k": "./bin/create-t2k.mjs"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.10.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"src",
|
|
31
|
+
"template",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node --test test/*.test.mjs",
|
|
37
|
+
"pack:check": "npm pack --dry-run",
|
|
38
|
+
"smoke:package": "node scripts/smoke-package.mjs",
|
|
39
|
+
"release:verify": "node scripts/verify-release-tag.mjs"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"provenance": true
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/scaffold.mjs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const templateRoot = path.join(packageRoot, "template");
|
|
9
|
+
|
|
10
|
+
export function parseArguments(argumentsList) {
|
|
11
|
+
const options = {
|
|
12
|
+
targetDirectory: "my-t2k-project",
|
|
13
|
+
install: true,
|
|
14
|
+
help: false,
|
|
15
|
+
version: false,
|
|
16
|
+
};
|
|
17
|
+
const positionals = [];
|
|
18
|
+
let parseOptions = true;
|
|
19
|
+
|
|
20
|
+
for (const argument of argumentsList) {
|
|
21
|
+
if (parseOptions && argument === "--") {
|
|
22
|
+
parseOptions = false;
|
|
23
|
+
} else if (parseOptions && ["-h", "--help"].includes(argument)) {
|
|
24
|
+
options.help = true;
|
|
25
|
+
} else if (parseOptions && ["-v", "--version"].includes(argument)) {
|
|
26
|
+
options.version = true;
|
|
27
|
+
} else if (parseOptions && argument === "--no-install") {
|
|
28
|
+
options.install = false;
|
|
29
|
+
} else if (parseOptions && argument === "--yes") {
|
|
30
|
+
// The scaffolder has no interactive choices; this keeps npx usage familiar.
|
|
31
|
+
} else if (parseOptions && argument.startsWith("-")) {
|
|
32
|
+
throw new Error(`Unknown option: ${argument}`);
|
|
33
|
+
} else {
|
|
34
|
+
positionals.push(argument);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (positionals.length > 1) {
|
|
39
|
+
throw new Error("Provide at most one project directory.");
|
|
40
|
+
}
|
|
41
|
+
if (positionals[0]) {
|
|
42
|
+
options.targetDirectory = positionals[0];
|
|
43
|
+
}
|
|
44
|
+
return options;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function packageNameFor(targetPath) {
|
|
48
|
+
const name = path.basename(targetPath).toLowerCase();
|
|
49
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(name)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"The project directory name must use lowercase letters, numbers, dots, dashes, or underscores."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function ensureEmptyDirectory(targetPath) {
|
|
58
|
+
try {
|
|
59
|
+
const stat = await fs.lstat(targetPath);
|
|
60
|
+
if (!stat.isDirectory()) {
|
|
61
|
+
throw new Error(`Target exists and is not a directory: ${targetPath}`);
|
|
62
|
+
}
|
|
63
|
+
const entries = await fs.readdir(targetPath);
|
|
64
|
+
if (entries.length > 0) {
|
|
65
|
+
throw new Error(`Target directory is not empty: ${targetPath}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
69
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function copyTemplate(sourceDirectory, targetDirectory, replacements) {
|
|
77
|
+
const entries = await fs.readdir(sourceDirectory, { withFileTypes: true });
|
|
78
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (entry.isSymbolicLink()) {
|
|
82
|
+
throw new Error(`Template symbolic links are not supported: ${entry.name}`);
|
|
83
|
+
}
|
|
84
|
+
const outputName = entry.name.endsWith(".template")
|
|
85
|
+
? entry.name.slice(0, -".template".length)
|
|
86
|
+
: entry.name;
|
|
87
|
+
const sourcePath = path.join(sourceDirectory, entry.name);
|
|
88
|
+
const targetPath = path.join(targetDirectory, outputName);
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
91
|
+
await copyTemplate(sourcePath, targetPath, replacements);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!entry.isFile()) {
|
|
95
|
+
throw new Error(`Unsupported template entry: ${entry.name}`);
|
|
96
|
+
}
|
|
97
|
+
let contents = await fs.readFile(sourcePath, "utf8");
|
|
98
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
99
|
+
contents = contents.replaceAll(token, value);
|
|
100
|
+
}
|
|
101
|
+
await fs.writeFile(targetPath, contents, "utf8");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function run(command, argumentsList, options) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const child = spawn(command, argumentsList, {
|
|
108
|
+
cwd: options.cwd,
|
|
109
|
+
env: process.env,
|
|
110
|
+
shell: false,
|
|
111
|
+
stdio: options.stdio,
|
|
112
|
+
});
|
|
113
|
+
child.once("error", reject);
|
|
114
|
+
child.once("exit", (code, signal) => {
|
|
115
|
+
if (code === 0) {
|
|
116
|
+
resolve();
|
|
117
|
+
} else {
|
|
118
|
+
reject(
|
|
119
|
+
new Error(
|
|
120
|
+
`${command} ${argumentsList.join(" ")} failed${
|
|
121
|
+
signal ? ` with signal ${signal}` : ` with exit code ${code}`
|
|
122
|
+
}.`
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function shellDisplay(value) {
|
|
131
|
+
return /^[a-zA-Z0-9_./-]+$/.test(value)
|
|
132
|
+
? value
|
|
133
|
+
: `'${value.replaceAll("'", `'\\''`)}'`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function scaffoldProject({
|
|
137
|
+
targetDirectory,
|
|
138
|
+
install = true,
|
|
139
|
+
cwd = process.cwd(),
|
|
140
|
+
stdout = process.stdout,
|
|
141
|
+
}) {
|
|
142
|
+
if (typeof targetDirectory !== "string" || !targetDirectory.trim()) {
|
|
143
|
+
throw new Error("Project directory is required.");
|
|
144
|
+
}
|
|
145
|
+
const targetPath = path.resolve(cwd, targetDirectory);
|
|
146
|
+
const projectName = packageNameFor(targetPath);
|
|
147
|
+
await ensureEmptyDirectory(targetPath);
|
|
148
|
+
await copyTemplate(templateRoot, targetPath, {
|
|
149
|
+
"{{PROJECT_NAME}}": projectName,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (install) {
|
|
153
|
+
stdout.write("Installing dependencies...\n");
|
|
154
|
+
await run(process.platform === "win32" ? "npm.cmd" : "npm", ["install"], {
|
|
155
|
+
cwd: targetPath,
|
|
156
|
+
stdio: "inherit",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const relativeTarget = path.relative(cwd, targetPath) || ".";
|
|
161
|
+
const commandTarget = path.isAbsolute(targetDirectory)
|
|
162
|
+
? targetPath
|
|
163
|
+
: relativeTarget;
|
|
164
|
+
stdout.write(`\nCreated ${projectName} in ${targetPath}\n\n`);
|
|
165
|
+
if (commandTarget !== ".") {
|
|
166
|
+
stdout.write(` cd ${shellDisplay(commandTarget)}\n`);
|
|
167
|
+
}
|
|
168
|
+
if (!install) {
|
|
169
|
+
stdout.write(" npm install\n");
|
|
170
|
+
}
|
|
171
|
+
stdout.write(" npm start\n\n");
|
|
172
|
+
stdout.write("The first run computes a recommendation; a human must still authorize it.\n");
|
|
173
|
+
|
|
174
|
+
return { targetPath, projectName };
|
|
175
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
This project is a local T2K governed-decision quickstart. Its data is fully
|
|
4
|
+
synthetic.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install
|
|
8
|
+
npm start
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The run performs five explicit steps:
|
|
12
|
+
|
|
13
|
+
1. validate `ontology-pack.json` against the published T2K schema;
|
|
14
|
+
2. compile the pack and resolve its decision template;
|
|
15
|
+
3. bind accepted facts from `decision-context.json`;
|
|
16
|
+
4. execute baseline and challenger policies;
|
|
17
|
+
5. compute held-out replay evidence and emit a recommendation for human review.
|
|
18
|
+
|
|
19
|
+
The output is not an autonomous authorization. The generated Decision Context
|
|
20
|
+
requires a `dispatch_owner` to review the evidence and authorize any action.
|
|
21
|
+
|
|
22
|
+
## Change the example
|
|
23
|
+
|
|
24
|
+
- Change current facts in `decision-context.json`.
|
|
25
|
+
- Change executable rules in `policies/*.json`.
|
|
26
|
+
- Add observed outcomes to `episodes/holdout.json` without reusing training data.
|
|
27
|
+
- Change concepts and decision contracts in `ontology-pack.json`.
|
|
28
|
+
|
|
29
|
+
The quickstart uses local files and `@t2kai/core`; it does not send data to a
|
|
30
|
+
hosted service. Persistence, receipts, promotion, and rollback require the
|
|
31
|
+
portable lifecycle runtime or hosted Studio and are not simulated here.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contextKey": "harborlight-dispatch-2026-07-18",
|
|
3
|
+
"decisionType": "operations.dispatch_overflow",
|
|
4
|
+
"question": "How should Harborlight respond to the current dispatch queue pressure?",
|
|
5
|
+
"objective": "Protect on-time service while using the least disruptive reversible response.",
|
|
6
|
+
"facts": [
|
|
7
|
+
{
|
|
8
|
+
"claimKey": "harborlight-current-queue-pressure",
|
|
9
|
+
"subjectRef": "harborlight-service-business",
|
|
10
|
+
"predicateRef": "demo.harborlight-field-service:service_business.queue_pressure_ratio",
|
|
11
|
+
"objectValue": 0.78,
|
|
12
|
+
"status": "accepted",
|
|
13
|
+
"sourceRefs": [
|
|
14
|
+
"manual:dispatch-board-2026-07-18"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"alternatives": [
|
|
19
|
+
"hold",
|
|
20
|
+
"authorize_overtime",
|
|
21
|
+
"rebalance_route"
|
|
22
|
+
],
|
|
23
|
+
"policies": [
|
|
24
|
+
"no_unreviewed_dispatch_change",
|
|
25
|
+
"connector_write_requires_rollback"
|
|
26
|
+
],
|
|
27
|
+
"authority": {
|
|
28
|
+
"requiredRole": "dispatch_owner",
|
|
29
|
+
"humanReviewRequired": true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"episodeId": "holdout-overtime-01",
|
|
4
|
+
"state": {
|
|
5
|
+
"facts": [
|
|
6
|
+
{
|
|
7
|
+
"objectValue": 0.61
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"loggedAction": "authorize_overtime",
|
|
12
|
+
"scalarReward": -0.5,
|
|
13
|
+
"learningMode": "supervised_feedback",
|
|
14
|
+
"behaviorProbability": null,
|
|
15
|
+
"guardrailViolation": false,
|
|
16
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"episodeId": "holdout-overtime-02",
|
|
20
|
+
"state": {
|
|
21
|
+
"facts": [
|
|
22
|
+
{
|
|
23
|
+
"objectValue": 0.62
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"loggedAction": "authorize_overtime",
|
|
28
|
+
"scalarReward": -0.5,
|
|
29
|
+
"learningMode": "supervised_feedback",
|
|
30
|
+
"behaviorProbability": null,
|
|
31
|
+
"guardrailViolation": false,
|
|
32
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"episodeId": "holdout-overtime-03",
|
|
36
|
+
"state": {
|
|
37
|
+
"facts": [
|
|
38
|
+
{
|
|
39
|
+
"objectValue": 0.63
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"loggedAction": "authorize_overtime",
|
|
44
|
+
"scalarReward": -0.5,
|
|
45
|
+
"learningMode": "supervised_feedback",
|
|
46
|
+
"behaviorProbability": null,
|
|
47
|
+
"guardrailViolation": false,
|
|
48
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"episodeId": "holdout-overtime-04",
|
|
52
|
+
"state": {
|
|
53
|
+
"facts": [
|
|
54
|
+
{
|
|
55
|
+
"objectValue": 0.64
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"loggedAction": "authorize_overtime",
|
|
60
|
+
"scalarReward": -0.5,
|
|
61
|
+
"learningMode": "supervised_feedback",
|
|
62
|
+
"behaviorProbability": null,
|
|
63
|
+
"guardrailViolation": false,
|
|
64
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"episodeId": "holdout-overtime-05",
|
|
68
|
+
"state": {
|
|
69
|
+
"facts": [
|
|
70
|
+
{
|
|
71
|
+
"objectValue": 0.65
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"loggedAction": "authorize_overtime",
|
|
76
|
+
"scalarReward": -0.5,
|
|
77
|
+
"learningMode": "supervised_feedback",
|
|
78
|
+
"behaviorProbability": null,
|
|
79
|
+
"guardrailViolation": false,
|
|
80
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"episodeId": "holdout-overtime-06",
|
|
84
|
+
"state": {
|
|
85
|
+
"facts": [
|
|
86
|
+
{
|
|
87
|
+
"objectValue": 0.66
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"loggedAction": "authorize_overtime",
|
|
92
|
+
"scalarReward": -0.5,
|
|
93
|
+
"learningMode": "supervised_feedback",
|
|
94
|
+
"behaviorProbability": null,
|
|
95
|
+
"guardrailViolation": false,
|
|
96
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"episodeId": "holdout-overtime-07",
|
|
100
|
+
"state": {
|
|
101
|
+
"facts": [
|
|
102
|
+
{
|
|
103
|
+
"objectValue": 0.67
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"loggedAction": "authorize_overtime",
|
|
108
|
+
"scalarReward": -0.5,
|
|
109
|
+
"learningMode": "supervised_feedback",
|
|
110
|
+
"behaviorProbability": null,
|
|
111
|
+
"guardrailViolation": false,
|
|
112
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"episodeId": "holdout-overtime-08",
|
|
116
|
+
"state": {
|
|
117
|
+
"facts": [
|
|
118
|
+
{
|
|
119
|
+
"objectValue": 0.68
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"loggedAction": "authorize_overtime",
|
|
124
|
+
"scalarReward": -0.5,
|
|
125
|
+
"learningMode": "supervised_feedback",
|
|
126
|
+
"behaviorProbability": null,
|
|
127
|
+
"guardrailViolation": false,
|
|
128
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"episodeId": "holdout-overtime-09",
|
|
132
|
+
"state": {
|
|
133
|
+
"facts": [
|
|
134
|
+
{
|
|
135
|
+
"objectValue": 0.69
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"loggedAction": "authorize_overtime",
|
|
140
|
+
"scalarReward": -0.5,
|
|
141
|
+
"learningMode": "supervised_feedback",
|
|
142
|
+
"behaviorProbability": null,
|
|
143
|
+
"guardrailViolation": false,
|
|
144
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"episodeId": "holdout-overtime-10",
|
|
148
|
+
"state": {
|
|
149
|
+
"facts": [
|
|
150
|
+
{
|
|
151
|
+
"objectValue": 0.7
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
"loggedAction": "authorize_overtime",
|
|
156
|
+
"scalarReward": -0.5,
|
|
157
|
+
"learningMode": "supervised_feedback",
|
|
158
|
+
"behaviorProbability": null,
|
|
159
|
+
"guardrailViolation": false,
|
|
160
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"episodeId": "holdout-rebalance-01",
|
|
164
|
+
"state": {
|
|
165
|
+
"facts": [
|
|
166
|
+
{
|
|
167
|
+
"objectValue": 0.72
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"loggedAction": "rebalance_route",
|
|
172
|
+
"scalarReward": 1,
|
|
173
|
+
"learningMode": "supervised_feedback",
|
|
174
|
+
"behaviorProbability": null,
|
|
175
|
+
"guardrailViolation": false,
|
|
176
|
+
"policyVersionId": "harborlight-dispatch@1.1.0"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"episodeId": "holdout-rebalance-02",
|
|
180
|
+
"state": {
|
|
181
|
+
"facts": [
|
|
182
|
+
{
|
|
183
|
+
"objectValue": 0.73
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
"loggedAction": "rebalance_route",
|
|
188
|
+
"scalarReward": 1,
|
|
189
|
+
"learningMode": "supervised_feedback",
|
|
190
|
+
"behaviorProbability": null,
|
|
191
|
+
"guardrailViolation": false,
|
|
192
|
+
"policyVersionId": "harborlight-dispatch@1.1.0"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"episodeId": "holdout-rebalance-03",
|
|
196
|
+
"state": {
|
|
197
|
+
"facts": [
|
|
198
|
+
{
|
|
199
|
+
"objectValue": 0.74
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
"loggedAction": "rebalance_route",
|
|
204
|
+
"scalarReward": 1,
|
|
205
|
+
"learningMode": "supervised_feedback",
|
|
206
|
+
"behaviorProbability": null,
|
|
207
|
+
"guardrailViolation": false,
|
|
208
|
+
"policyVersionId": "harborlight-dispatch@1.1.0"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"episodeId": "holdout-rebalance-04",
|
|
212
|
+
"state": {
|
|
213
|
+
"facts": [
|
|
214
|
+
{
|
|
215
|
+
"objectValue": 0.75
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
"loggedAction": "rebalance_route",
|
|
220
|
+
"scalarReward": 1,
|
|
221
|
+
"learningMode": "supervised_feedback",
|
|
222
|
+
"behaviorProbability": null,
|
|
223
|
+
"guardrailViolation": false,
|
|
224
|
+
"policyVersionId": "harborlight-dispatch@1.1.0"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"episodeId": "holdout-rebalance-05",
|
|
228
|
+
"state": {
|
|
229
|
+
"facts": [
|
|
230
|
+
{
|
|
231
|
+
"objectValue": 0.76
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
"loggedAction": "rebalance_route",
|
|
236
|
+
"scalarReward": 1,
|
|
237
|
+
"learningMode": "supervised_feedback",
|
|
238
|
+
"behaviorProbability": null,
|
|
239
|
+
"guardrailViolation": false,
|
|
240
|
+
"policyVersionId": "harborlight-dispatch@1.1.0"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"episodeId": "holdout-rebalance-06",
|
|
244
|
+
"state": {
|
|
245
|
+
"facts": [
|
|
246
|
+
{
|
|
247
|
+
"objectValue": 0.77
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
"loggedAction": "rebalance_route",
|
|
252
|
+
"scalarReward": 1,
|
|
253
|
+
"learningMode": "supervised_feedback",
|
|
254
|
+
"behaviorProbability": null,
|
|
255
|
+
"guardrailViolation": false,
|
|
256
|
+
"policyVersionId": "harborlight-dispatch@1.1.0"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"episodeId": "holdout-hold-01",
|
|
260
|
+
"state": {
|
|
261
|
+
"facts": [
|
|
262
|
+
{
|
|
263
|
+
"objectValue": 0.21
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"loggedAction": "hold",
|
|
268
|
+
"scalarReward": 0,
|
|
269
|
+
"learningMode": "supervised_feedback",
|
|
270
|
+
"behaviorProbability": null,
|
|
271
|
+
"guardrailViolation": false,
|
|
272
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"episodeId": "holdout-hold-02",
|
|
276
|
+
"state": {
|
|
277
|
+
"facts": [
|
|
278
|
+
{
|
|
279
|
+
"objectValue": 0.24
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
"loggedAction": "hold",
|
|
284
|
+
"scalarReward": 0,
|
|
285
|
+
"learningMode": "supervised_feedback",
|
|
286
|
+
"behaviorProbability": null,
|
|
287
|
+
"guardrailViolation": false,
|
|
288
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"episodeId": "holdout-hold-03",
|
|
292
|
+
"state": {
|
|
293
|
+
"facts": [
|
|
294
|
+
{
|
|
295
|
+
"objectValue": 0.27
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
"loggedAction": "hold",
|
|
300
|
+
"scalarReward": 0,
|
|
301
|
+
"learningMode": "supervised_feedback",
|
|
302
|
+
"behaviorProbability": null,
|
|
303
|
+
"guardrailViolation": false,
|
|
304
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"episodeId": "holdout-hold-04",
|
|
308
|
+
"state": {
|
|
309
|
+
"facts": [
|
|
310
|
+
{
|
|
311
|
+
"objectValue": 0.3
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
"loggedAction": "hold",
|
|
316
|
+
"scalarReward": 0,
|
|
317
|
+
"learningMode": "supervised_feedback",
|
|
318
|
+
"behaviorProbability": null,
|
|
319
|
+
"guardrailViolation": false,
|
|
320
|
+
"policyVersionId": "harborlight-dispatch@1.0.0"
|
|
321
|
+
}
|
|
322
|
+
]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://t2k.ai/schemas/t2k-ontology-pack.v1.schema.json",
|
|
3
|
+
"manifestType": "t2k.ontology-pack",
|
|
4
|
+
"manifestVersion": "1.0",
|
|
5
|
+
"ontologyVersion": "1.0.0",
|
|
6
|
+
"ontologyId": "demo.harborlight-field-service",
|
|
7
|
+
"label": "Harborlight Field Service",
|
|
8
|
+
"description": "A fully synthetic ontology pack for a governed dispatch decision loop.",
|
|
9
|
+
"packKind": "vertical",
|
|
10
|
+
"status": "review",
|
|
11
|
+
"scope": {
|
|
12
|
+
"domain": "field_service",
|
|
13
|
+
"description": "Synthetic service dispatch operations for the T2K quickstart."
|
|
14
|
+
},
|
|
15
|
+
"objectTypes": [
|
|
16
|
+
{
|
|
17
|
+
"id": "service_business",
|
|
18
|
+
"label": "Service business",
|
|
19
|
+
"family": "Operating organization",
|
|
20
|
+
"nodeKind": "operating-entity",
|
|
21
|
+
"purpose": "A fictional field-service operator that dispatches work crews.",
|
|
22
|
+
"identity": [
|
|
23
|
+
"business_id"
|
|
24
|
+
],
|
|
25
|
+
"properties": [
|
|
26
|
+
{
|
|
27
|
+
"id": "queue_pressure_ratio",
|
|
28
|
+
"valueType": "number",
|
|
29
|
+
"required": false,
|
|
30
|
+
"description": "Open urgent jobs divided by available dispatch capacity.",
|
|
31
|
+
"authorityDomain": "operations",
|
|
32
|
+
"temporal": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "on_time_completion_rate",
|
|
36
|
+
"valueType": "number",
|
|
37
|
+
"required": false,
|
|
38
|
+
"description": "Share of scheduled work completed inside the promised window.",
|
|
39
|
+
"authorityDomain": "operations",
|
|
40
|
+
"temporal": true
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"decisionTemplates": [
|
|
46
|
+
{
|
|
47
|
+
"id": "route_overflow",
|
|
48
|
+
"question": "How should Harborlight respond to the current dispatch queue pressure?",
|
|
49
|
+
"decisionType": "operations.dispatch_overflow",
|
|
50
|
+
"requiredContext": [],
|
|
51
|
+
"requiredFacts": [
|
|
52
|
+
"demo.harborlight-field-service:service_business.queue_pressure_ratio"
|
|
53
|
+
],
|
|
54
|
+
"objective": "Protect on-time service while using the least disruptive reversible response.",
|
|
55
|
+
"successMeasure": "The on-time completion rate improves relative to the pre-decision baseline.",
|
|
56
|
+
"alternatives": [
|
|
57
|
+
"hold",
|
|
58
|
+
"authorize_overtime",
|
|
59
|
+
"rebalance_route"
|
|
60
|
+
],
|
|
61
|
+
"criteria": [
|
|
62
|
+
"on_time_completion",
|
|
63
|
+
"reversibility",
|
|
64
|
+
"crew_load"
|
|
65
|
+
],
|
|
66
|
+
"comparisonMethod": "reference_rule_set",
|
|
67
|
+
"policies": [
|
|
68
|
+
"no_unreviewed_dispatch_change"
|
|
69
|
+
],
|
|
70
|
+
"authority": "dispatch_owner",
|
|
71
|
+
"riskLevel": "L1",
|
|
72
|
+
"allowedActionProposals": [
|
|
73
|
+
"dispatch_plan.apply"
|
|
74
|
+
],
|
|
75
|
+
"rollbackExpectation": "Restore the prior dispatch plan if connector reconciliation fails.",
|
|
76
|
+
"outcomeMeasures": [
|
|
77
|
+
"demo.harborlight-field-service:service_business.on_time_completion_rate"
|
|
78
|
+
],
|
|
79
|
+
"reviewHorizon": "Review after the seven-day service window.",
|
|
80
|
+
"learningContract": {
|
|
81
|
+
"mode": "supervised_feedback",
|
|
82
|
+
"stateSchema": {
|
|
83
|
+
"required": [
|
|
84
|
+
"facts.0.objectValue"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"actionSchema": {
|
|
88
|
+
"allowed": [
|
|
89
|
+
"hold",
|
|
90
|
+
"authorize_overtime",
|
|
91
|
+
"rebalance_route"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
"rewardSpec": [
|
|
95
|
+
{
|
|
96
|
+
"measureRef": "demo.harborlight-field-service:service_business.on_time_completion_rate",
|
|
97
|
+
"label": "On-time completion rate",
|
|
98
|
+
"direction": "maximize",
|
|
99
|
+
"weight": 1,
|
|
100
|
+
"required": true,
|
|
101
|
+
"guardrail": false,
|
|
102
|
+
"unit": "ratio",
|
|
103
|
+
"observationWindow": "7d",
|
|
104
|
+
"aggregation": "latest",
|
|
105
|
+
"baselineMethod": "explicit",
|
|
106
|
+
"attributionMethod": "human_review"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"observationSchedule": [
|
|
110
|
+
"7d"
|
|
111
|
+
],
|
|
112
|
+
"terminalConditions": [
|
|
113
|
+
"Seven-day service window complete"
|
|
114
|
+
],
|
|
115
|
+
"explorationPolicy": {
|
|
116
|
+
"mode": "none"
|
|
117
|
+
},
|
|
118
|
+
"safetyConstraints": [
|
|
119
|
+
"A human dispatch owner authorizes every action",
|
|
120
|
+
"Every connector write has a rollback contract and receipt"
|
|
121
|
+
],
|
|
122
|
+
"promotionCriteria": {
|
|
123
|
+
"minimumPassingReplays": 1,
|
|
124
|
+
"minimumHeldOutEpisodes": 20
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.10.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/run.mjs",
|
|
11
|
+
"check": "node src/run.mjs"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@t2kai/core": "^0.1.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"policyKey": "harborlight-dispatch",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"specification": {
|
|
5
|
+
"referencePolicy": {
|
|
6
|
+
"rules": [
|
|
7
|
+
{
|
|
8
|
+
"all": [
|
|
9
|
+
{
|
|
10
|
+
"path": "facts.0.objectValue",
|
|
11
|
+
"operator": "gte",
|
|
12
|
+
"value": 0.6
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"action": "authorize_overtime"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"defaultAction": "hold",
|
|
19
|
+
"evaluation": {
|
|
20
|
+
"minimumEpisodes": 20,
|
|
21
|
+
"minimumImprovement": 0.05,
|
|
22
|
+
"confidenceZ": 1.96,
|
|
23
|
+
"minimumCoverage": 0.2
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"policyKey": "harborlight-dispatch",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"status": "proposed",
|
|
5
|
+
"specification": {
|
|
6
|
+
"referencePolicy": {
|
|
7
|
+
"rules": [
|
|
8
|
+
{
|
|
9
|
+
"all": [
|
|
10
|
+
{
|
|
11
|
+
"path": "facts.0.objectValue",
|
|
12
|
+
"operator": "gte",
|
|
13
|
+
"value": 0.6
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"action": "rebalance_route"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"defaultAction": "hold",
|
|
20
|
+
"evaluation": {
|
|
21
|
+
"minimumEpisodes": 20,
|
|
22
|
+
"minimumImprovement": 0.05,
|
|
23
|
+
"confidenceZ": 1.96,
|
|
24
|
+
"minimumCoverage": 0.2
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
aggregatePolicyRewards,
|
|
8
|
+
evaluateReferencePolicy,
|
|
9
|
+
evaluateReferenceReplay,
|
|
10
|
+
validateOntologyPackManifest,
|
|
11
|
+
} from "@t2kai/core";
|
|
12
|
+
import { compileOntologyPackSet } from "@t2kai/core/compiler";
|
|
13
|
+
|
|
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"));
|
|
17
|
+
|
|
18
|
+
const [manifest, context, baseline, candidate, episodes] = await Promise.all([
|
|
19
|
+
readJson("ontology-pack.json"),
|
|
20
|
+
readJson("decision-context.json"),
|
|
21
|
+
readJson("policies/baseline.json"),
|
|
22
|
+
readJson("policies/candidate.json"),
|
|
23
|
+
readJson("episodes/holdout.json"),
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const validation = validateOntologyPackManifest(manifest);
|
|
27
|
+
assert.equal(
|
|
28
|
+
validation.valid,
|
|
29
|
+
true,
|
|
30
|
+
`Ontology validation failed: ${JSON.stringify(validation.errors)}`
|
|
31
|
+
);
|
|
32
|
+
const compilation = compileOntologyPackSet({
|
|
33
|
+
manifests: [manifest],
|
|
34
|
+
roots: [{ ontologyId: manifest.ontologyId, version: manifest.ontologyVersion }],
|
|
35
|
+
});
|
|
36
|
+
assert.equal(compilation.status, "valid", JSON.stringify(compilation.diagnostics));
|
|
37
|
+
|
|
38
|
+
const decisionTemplate = compilation.definitions.find(
|
|
39
|
+
(definition) =>
|
|
40
|
+
definition.definitionKind === "decision_template" &&
|
|
41
|
+
definition.body.decisionType === context.decisionType
|
|
42
|
+
);
|
|
43
|
+
assert.ok(decisionTemplate, `No compiled template for ${context.decisionType}`);
|
|
44
|
+
const acceptedFactRefs = new Set(
|
|
45
|
+
context.facts
|
|
46
|
+
.filter((fact) => fact.status === "accepted")
|
|
47
|
+
.map((fact) => fact.predicateRef)
|
|
48
|
+
);
|
|
49
|
+
for (const requiredFact of decisionTemplate.body.requiredFacts ?? []) {
|
|
50
|
+
assert.ok(acceptedFactRefs.has(requiredFact), `Missing accepted fact: ${requiredFact}`);
|
|
51
|
+
}
|
|
52
|
+
assert.equal(
|
|
53
|
+
context.authority.humanReviewRequired,
|
|
54
|
+
true,
|
|
55
|
+
"The quickstart must not silently authorize its recommendation."
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const state = { facts: context.facts };
|
|
59
|
+
const baselineAction = evaluateReferencePolicy(baseline.specification, state);
|
|
60
|
+
const candidateAction = evaluateReferencePolicy(candidate.specification, state);
|
|
61
|
+
const replay = evaluateReferenceReplay({
|
|
62
|
+
candidateSpecification: candidate.specification,
|
|
63
|
+
baselineSpecification: baseline.specification,
|
|
64
|
+
episodes: episodes.map(({ policyVersionId: _policyVersionId, ...episode }) => episode),
|
|
65
|
+
});
|
|
66
|
+
assert.equal(replay.status, "passed", "The challenger must pass computed replay.");
|
|
67
|
+
assert.equal(replay.candidate.guardrailViolations, 0);
|
|
68
|
+
|
|
69
|
+
const rewardAggregates = aggregatePolicyRewards(
|
|
70
|
+
episodes.map((episode) => ({
|
|
71
|
+
policyVersionId: episode.policyVersionId,
|
|
72
|
+
scalarReward: episode.scalarReward,
|
|
73
|
+
guardrailViolation: episode.guardrailViolation,
|
|
74
|
+
}))
|
|
75
|
+
);
|
|
76
|
+
const result = {
|
|
77
|
+
ontology: `${manifest.ontologyId}@${manifest.ontologyVersion}`,
|
|
78
|
+
resolutionHash: compilation.resolutionHash,
|
|
79
|
+
decisionContext: {
|
|
80
|
+
contextKey: context.contextKey,
|
|
81
|
+
decisionType: context.decisionType,
|
|
82
|
+
acceptedFacts: acceptedFactRefs.size,
|
|
83
|
+
},
|
|
84
|
+
reasoning: {
|
|
85
|
+
baseline: { version: baseline.version, action: baselineAction },
|
|
86
|
+
candidate: { version: candidate.version, action: candidateAction },
|
|
87
|
+
},
|
|
88
|
+
replay: {
|
|
89
|
+
status: replay.status,
|
|
90
|
+
sampleSize: replay.sampleSize,
|
|
91
|
+
estimatedImprovement: replay.estimatedImprovement,
|
|
92
|
+
improvementConfidenceLower: replay.improvementConfidenceLower,
|
|
93
|
+
candidateCoverage: replay.candidate.coverage,
|
|
94
|
+
baselineCoverage: replay.baseline.coverage,
|
|
95
|
+
guardrailViolations: replay.candidate.guardrailViolations,
|
|
96
|
+
},
|
|
97
|
+
rewardAggregates,
|
|
98
|
+
recommendation: {
|
|
99
|
+
action: candidateAction,
|
|
100
|
+
policyVersion: candidate.version,
|
|
101
|
+
status: "eligible_for_human_review",
|
|
102
|
+
},
|
|
103
|
+
authorization: {
|
|
104
|
+
requiredRole: context.authority.requiredRole,
|
|
105
|
+
status: "not_authorized",
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
console.log(JSON.stringify(result, null, 2));
|