@kontourai/lookout 0.3.4 → 0.3.6
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 +34 -0
- package/dist/src/cli.d.ts +3 -3
- package/dist/src/cli.js +8 -8
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/source-store.d.ts +35 -0
- package/dist/src/source-store.js +14 -0
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -279,6 +279,40 @@ Forage's SSRF-guarded fetcher), `fetchOptions`, and
|
|
|
279
279
|
`clock` — so checks run with no live network or timers in tests. Injecting either
|
|
280
280
|
`fetchSource` or `fetchOptions.fetch` overrides the default guarded transport.
|
|
281
281
|
|
|
282
|
+
### Bring your own source store
|
|
283
|
+
|
|
284
|
+
The registry file is a convenience, not the contract. What check
|
|
285
|
+
classification, drift emission, and observation lineage actually consume is
|
|
286
|
+
source identity — enumerate sources, resolve one by exact id — and that
|
|
287
|
+
contract is the `SourceStore` interface: `list()` and `get(id)`, each
|
|
288
|
+
returning its value directly or as a promise. `LookoutRegistry` (what
|
|
289
|
+
`loadRegistry` returns) is the file-backed implementation; nothing downstream
|
|
290
|
+
can tell implementations apart.
|
|
291
|
+
|
|
292
|
+
Two application shapes motivate the seam. An application whose canonical
|
|
293
|
+
source of truth is its own database — reviewed rows in Postgres or D1, not a
|
|
294
|
+
JSON file — builds `LookoutSource` values from those rows and should not have
|
|
295
|
+
to adopt a second store of record to inherit lineage, continuity, and CHECK
|
|
296
|
+
classification. And a host that owns its own acquisition and run state wants
|
|
297
|
+
to hand Lookout sources it already holds in memory. Both were previously
|
|
298
|
+
re-implementing identity handling around the registry; either can now do:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { inMemorySourceStore } from "@kontourai/lookout";
|
|
302
|
+
|
|
303
|
+
const store = inMemorySourceStore(sourcesBuiltFromYourRows);
|
|
304
|
+
const results = await runner.checkAll(await store.list());
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`inMemorySourceStore` applies exactly the validation `loadRegistry` applies
|
|
308
|
+
to a file — duplicate ids, URL shape, per-kind field rules, every issue
|
|
309
|
+
reported at once — so source-id uniqueness is enforced structurally rather
|
|
310
|
+
than by review convention. A database-backed application can also implement
|
|
311
|
+
`SourceStore` directly (both methods may be async) and query lazily;
|
|
312
|
+
`runCli`'s `loadRegistry` option accepts any `SourceStore`. Keep ids stable
|
|
313
|
+
either way: snapshot identity and observation lineage key off `id`, so
|
|
314
|
+
changing one starts a new history.
|
|
315
|
+
|
|
282
316
|
## Observe changed sources without re-extracting unchanged ones
|
|
283
317
|
|
|
284
318
|
`createObserveExtractDiff` is an optional library composition for callers that
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { Writable } from "node:stream";
|
|
2
2
|
import { type CheckRunner } from "./check-runner.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { SourceStore } from "./source-store.js";
|
|
4
4
|
import { type DriftResult } from "./drift-emission.js";
|
|
5
5
|
export interface RunCliOptions {
|
|
6
6
|
argv?: string[];
|
|
7
7
|
stdout?: Pick<Writable, "write">;
|
|
8
8
|
stderr?: Pick<Writable, "write">;
|
|
9
|
-
loadRegistry
|
|
9
|
+
loadRegistry?(path?: string): Promise<SourceStore>;
|
|
10
10
|
runner?: CheckRunner;
|
|
11
11
|
readObservation?: (path: string) => Promise<unknown>;
|
|
12
|
-
emitDrift
|
|
12
|
+
emitDrift?(sourceId: string, value: unknown, store: SourceStore, observationRoot?: string): Promise<DriftResult>;
|
|
13
13
|
}
|
|
14
14
|
export declare function runCli(options?: RunCliOptions): Promise<number>;
|
package/dist/src/cli.js
CHANGED
|
@@ -13,16 +13,16 @@ export async function runCli(options = {}) {
|
|
|
13
13
|
stderr.write(`${parsed}\n`);
|
|
14
14
|
return 2;
|
|
15
15
|
}
|
|
16
|
-
let
|
|
16
|
+
let store;
|
|
17
17
|
try {
|
|
18
|
-
|
|
18
|
+
store = await (options.loadRegistry ?? loadRegistry)(parsed.registryPath);
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
21
21
|
stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
22
22
|
return 1;
|
|
23
23
|
}
|
|
24
24
|
if (parsed.command === "emit-drift") {
|
|
25
|
-
const source =
|
|
25
|
+
const source = await store.get(parsed.id);
|
|
26
26
|
if (!source) {
|
|
27
27
|
stderr.write(`Unknown source id: ${parsed.id}\n`);
|
|
28
28
|
return 1;
|
|
@@ -35,7 +35,7 @@ export async function runCli(options = {}) {
|
|
|
35
35
|
stderr.write(`Could not read observation: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
36
36
|
return 1;
|
|
37
37
|
}
|
|
38
|
-
const result = await (options.emitDrift ?? emitDrift)(parsed.id, value,
|
|
38
|
+
const result = await (options.emitDrift ?? emitDrift)(parsed.id, value, store, parsed.observationRoot);
|
|
39
39
|
if (!result.ok) {
|
|
40
40
|
stderr.write(`${result.error.kind}: ${result.error.message}\n`);
|
|
41
41
|
return 1;
|
|
@@ -47,12 +47,12 @@ export async function runCli(options = {}) {
|
|
|
47
47
|
store: createLookoutSnapshotStore(parsed.snapshotRoot),
|
|
48
48
|
});
|
|
49
49
|
if (parsed.all) {
|
|
50
|
-
const results = await runner.checkAll(
|
|
50
|
+
const results = await runner.checkAll(await store.list());
|
|
51
51
|
for (const result of results)
|
|
52
52
|
stdout.write(`${JSON.stringify(result)}\n`);
|
|
53
53
|
return 0;
|
|
54
54
|
}
|
|
55
|
-
const source =
|
|
55
|
+
const source = await store.get(parsed.id);
|
|
56
56
|
if (!source) {
|
|
57
57
|
stderr.write(`Unknown source id: ${parsed.id}\n`);
|
|
58
58
|
return 1;
|
|
@@ -164,10 +164,10 @@ function cliEntities(observation) {
|
|
|
164
164
|
}
|
|
165
165
|
return [...grouped].map(([key, proposals]) => ({ key, proposals }));
|
|
166
166
|
}
|
|
167
|
-
async function emitDrift(sourceId, value,
|
|
167
|
+
async function emitDrift(sourceId, value, store, observationRoot) {
|
|
168
168
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
169
169
|
return { ok: false, error: { kind: "invalid-input", message: "Observation document must be an object" } };
|
|
170
170
|
const document = value;
|
|
171
|
-
const source =
|
|
171
|
+
const source = (await store.get(sourceId));
|
|
172
172
|
return createDriftEmitter({ store: createObservationStore({ root: observationRoot }) }).emit({ source, current: document.observation, check: document.check, callbacks: { selectEntities: cliEntities, entityIdentity: (entity) => entity.key, proposalsFor: (entity) => entity.proposals, fieldIdentity: (_entity, proposal) => proposal.fieldPath } });
|
|
173
173
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type { ChangedResult, CheckResult, CheckResultCommon, ErrorResult, Lookou
|
|
|
6
6
|
export { defaultProviderResolver } from "./provider-resolution.js";
|
|
7
7
|
export type { ProviderResolver } from "./provider-resolution.js";
|
|
8
8
|
export { loadRegistry, LookoutRegistry, parseRegistry, RegistryValidationError, } from "./registry.js";
|
|
9
|
+
export { inMemorySourceStore } from "./source-store.js";
|
|
10
|
+
export type { SourceStore } from "./source-store.js";
|
|
9
11
|
export type { ExtractableLookoutSource, LookoutRegistryDocument, LookoutSource, LookoutSourceKind, RenderPolicy, StructuredFileFormat, StructuredFileLookoutSource, } from "./registry.js";
|
|
10
12
|
export { createLookoutSnapshotStore, resolveLookoutSnapshot, } from "./snapshot-store.js";
|
|
11
13
|
export type { ResolveLookoutSnapshotOptions } from "./snapshot-store.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { runCli } from "./cli.js";
|
|
|
2
2
|
export { createCheckRunner } from "./check-runner.js";
|
|
3
3
|
export { defaultProviderResolver } from "./provider-resolution.js";
|
|
4
4
|
export { loadRegistry, LookoutRegistry, parseRegistry, RegistryValidationError, } from "./registry.js";
|
|
5
|
+
export { inMemorySourceStore } from "./source-store.js";
|
|
5
6
|
export { createLookoutSnapshotStore, resolveLookoutSnapshot, } from "./snapshot-store.js";
|
|
6
7
|
export { canonicalValueKey } from "./canonical-value.js";
|
|
7
8
|
export { compareStructural, diffKeyedMultiset } from "./structural-diff.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type LookoutRegistry, type LookoutSource } from "./registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* The seam between where sources live and everything Lookout does with them.
|
|
4
|
+
*
|
|
5
|
+
* Check classification, drift emission, and observation lineage all key off a
|
|
6
|
+
* source's identity (`LookoutSource.id`), not off the file the source was
|
|
7
|
+
* declared in. A `SourceStore` is the minimal contract those parts actually
|
|
8
|
+
* consume: enumerate sources and resolve one by exact id. The bundled file
|
|
9
|
+
* registry (`loadRegistry` / `LookoutRegistry`) is one implementation; an
|
|
10
|
+
* application whose canonical source of truth is its own database can
|
|
11
|
+
* implement this directly, or materialize rows through
|
|
12
|
+
* {@link inMemorySourceStore}, and still inherit lineage, continuity, and
|
|
13
|
+
* CHECK classification.
|
|
14
|
+
*
|
|
15
|
+
* Methods may return values synchronously or as promises so that an
|
|
16
|
+
* in-memory store stays allocation-free while a database-backed store can
|
|
17
|
+
* query lazily. Callers must `await` both methods.
|
|
18
|
+
*/
|
|
19
|
+
export interface SourceStore {
|
|
20
|
+
/** Every source, in the store's canonical order. */
|
|
21
|
+
list(): readonly LookoutSource[] | Promise<readonly LookoutSource[]>;
|
|
22
|
+
/** Exact-id lookup; `undefined` when the id is not present. */
|
|
23
|
+
get(id: string): LookoutSource | undefined | Promise<LookoutSource | undefined>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a validated in-memory {@link SourceStore} from sources an application
|
|
27
|
+
* constructed itself (for example, from its own database rows).
|
|
28
|
+
*
|
|
29
|
+
* Runs exactly the validation `loadRegistry` applies to a registry file —
|
|
30
|
+
* duplicate ids, URL shape, per-kind field rules — and throws the same
|
|
31
|
+
* `RegistryValidationError` reporting every issue at once. The returned store
|
|
32
|
+
* is a `LookoutRegistry`, so file-backed and in-memory sources are
|
|
33
|
+
* indistinguishable downstream.
|
|
34
|
+
*/
|
|
35
|
+
export declare function inMemorySourceStore(sources: readonly LookoutSource[]): LookoutRegistry;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseRegistry } from "./registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a validated in-memory {@link SourceStore} from sources an application
|
|
4
|
+
* constructed itself (for example, from its own database rows).
|
|
5
|
+
*
|
|
6
|
+
* Runs exactly the validation `loadRegistry` applies to a registry file —
|
|
7
|
+
* duplicate ids, URL shape, per-kind field rules — and throws the same
|
|
8
|
+
* `RegistryValidationError` reporting every issue at once. The returned store
|
|
9
|
+
* is a `LookoutRegistry`, so file-backed and in-memory sources are
|
|
10
|
+
* indistinguishable downstream.
|
|
11
|
+
*/
|
|
12
|
+
export function inMemorySourceStore(sources) {
|
|
13
|
+
return parseRegistry({ version: 1, sources });
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/lookout",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "A small source registry and drift-check runner built on Forage snapshots.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,23 +35,26 @@
|
|
|
35
35
|
"build": "rm -rf dist && tsc",
|
|
36
36
|
"prepare": "npm run build",
|
|
37
37
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"test": "npm run build && node
|
|
38
|
+
"test": "npm run build && node scripts/run-tests.mjs",
|
|
39
39
|
"verify": "npm run check:content-boundary && npm run check:decisions && npm run typecheck && npm test && npm run check:pack && npm run check:consumer",
|
|
40
40
|
"check:content-boundary": "node scripts/check-content-boundary.cjs",
|
|
41
41
|
"check:decisions": "node scripts/check-decisions.cjs check",
|
|
42
42
|
"gen:decisions-index": "node scripts/check-decisions.cjs gen-index",
|
|
43
43
|
"check:pack": "node scripts/check-package-contents.mjs",
|
|
44
|
-
"check:consumer": "node scripts/check-installed-consumer.mjs"
|
|
44
|
+
"check:consumer": "node scripts/check-installed-consumer.mjs",
|
|
45
|
+
"workflow:sidecar": "flow-agents-workflow-sidecar",
|
|
46
|
+
"workflow:validate-artifacts": "flow-agents-validate-artifacts"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
47
49
|
"@kontourai/datum": "0.7.0",
|
|
48
50
|
"@kontourai/forage": "0.5.0",
|
|
49
|
-
"@kontourai/traverse": "0.
|
|
51
|
+
"@kontourai/traverse": "0.25.0"
|
|
50
52
|
},
|
|
51
53
|
"devDependencies": {
|
|
52
|
-
"@kontourai/
|
|
53
|
-
"@
|
|
54
|
-
"
|
|
54
|
+
"@kontourai/flow-agents": "5.5.0",
|
|
55
|
+
"@kontourai/survey": "2.4.0",
|
|
56
|
+
"@types/node": "^26.1.2",
|
|
57
|
+
"typescript": "^7.0.2"
|
|
55
58
|
},
|
|
56
59
|
"engines": {
|
|
57
60
|
"node": ">=22"
|