@orkestrel/brief 0.0.4 → 0.0.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.
@@ -45,6 +45,36 @@ var RISK_SEVERITIES = Object.freeze([
45
45
  "high"
46
46
  ]);
47
47
  /**
48
+ * Every published `Interpretation` member name, frozen.
49
+ *
50
+ * @remarks
51
+ * The capture list `BriefCompiler` hands `captureValue` at each interpret door — the borrowed
52
+ * engine's return, and the caller's supplied interpretation. A class instance carries its
53
+ * contract on the prototype, so the captured view materializes exactly the members named here,
54
+ * and a name missing from the list is a member the view drops.
55
+ *
56
+ * The `satisfies` clause refuses a name `Interpretation` does not declare, and it holds the
57
+ * element type at the listed names rather than widening it to `string`. That is what lets the
58
+ * equality assertion beside the capture cases refuse a list that has fallen short of the
59
+ * published shape.
60
+ */
61
+ var INTERPRETATION_MEMBERS = Object.freeze([
62
+ "text",
63
+ "normalized",
64
+ "intent",
65
+ "entities",
66
+ "subject",
67
+ "definition",
68
+ "mappings",
69
+ "ambiguities",
70
+ "prompt",
71
+ "stages",
72
+ "failures",
73
+ "complete",
74
+ "confidence",
75
+ "digest"
76
+ ]);
77
+ /**
48
78
  * `16` — the default turn cap `briefToGoal` renders.
49
79
  *
50
80
  * @remarks
@@ -370,6 +400,84 @@ var isBrief = (0, _orkestrel_contract.recordOf)({
370
400
  //#endregion
371
401
  //#region src/core/cloners.ts
372
402
  /**
403
+ * Captures one stable, frozen view of a foreign contract value.
404
+ *
405
+ * @remarks
406
+ * Rebuilds the root and every reachable plain container from its own enumerable members.
407
+ * Unknown own members survive. Each published member absent from that copied own set is read
408
+ * once and materialized, which admits a class that supplies its contract through prototype
409
+ * accessors without leaving later reads attached to the live instance. Non-container leaves
410
+ * retain their identity, including functions that `structuredClone` cannot carry.
411
+ *
412
+ * @param source - The foreign value to capture.
413
+ * @param members - The published root member names to materialize when absent from its own set.
414
+ * @returns A deeply frozen plain view, or `source` itself when it is a primitive.
415
+ *
416
+ * @example
417
+ * ```ts
418
+ * import { captureValue } from '@orkestrel/brief'
419
+ *
420
+ * const leaf = () => 'ready'
421
+ * const owned = captureValue({ leaf }, ['leaf'])
422
+ * Reflect.get(owned, 'leaf') === leaf // true — an uncloneable leaf keeps its identity
423
+ * Object.isFrozen(owned) // true
424
+ * ```
425
+ */
426
+ function captureValue(source, members) {
427
+ if (source === null || typeof source !== "object" && typeof source !== "function") return source;
428
+ const target = Array.isArray(source) ? [] : Object.create(null);
429
+ const seen = new WeakMap([[source, target]]);
430
+ const captured = [target];
431
+ const pending = [[
432
+ source,
433
+ target,
434
+ members
435
+ ]];
436
+ while (pending.length > 0) {
437
+ const frame = pending.pop();
438
+ if (frame === void 0) continue;
439
+ const [current, view, expected] = frame;
440
+ const entries = [];
441
+ const copied = /* @__PURE__ */ new Set();
442
+ for (const key of Reflect.ownKeys(current)) {
443
+ const descriptor = Reflect.getOwnPropertyDescriptor(current, key);
444
+ if (descriptor === void 0 || !descriptor.enumerable) continue;
445
+ copied.add(key);
446
+ entries.push([key, "value" in descriptor ? descriptor.value : Reflect.get(current, key)]);
447
+ }
448
+ for (const key of expected ?? []) if (!copied.has(key)) entries.push([key, Reflect.get(current, key)]);
449
+ for (const [key, value] of entries) {
450
+ let owned = value;
451
+ if (value !== null && typeof value === "object") {
452
+ const existing = seen.get(value);
453
+ if (existing !== void 0) owned = existing;
454
+ else {
455
+ const prototype = Reflect.getPrototypeOf(value);
456
+ if (Array.isArray(value) || prototype === null || prototype === Object.prototype) {
457
+ const branch = Array.isArray(value) ? [] : Object.create(null);
458
+ seen.set(value, branch);
459
+ captured.push(branch);
460
+ pending.push([
461
+ value,
462
+ branch,
463
+ void 0
464
+ ]);
465
+ owned = branch;
466
+ }
467
+ }
468
+ }
469
+ Reflect.defineProperty(view, key, {
470
+ value: owned,
471
+ enumerable: true,
472
+ configurable: false,
473
+ writable: false
474
+ });
475
+ }
476
+ }
477
+ for (const view of captured) Object.freeze(view);
478
+ return target;
479
+ }
480
+ /**
373
481
  * Return a deeply owned, deeply frozen copy of a brief, refusing anything off-contract.
374
482
  *
375
483
  * @remarks
@@ -1164,6 +1272,15 @@ function errorToMessage(error) {
1164
1272
  * nothing, so it is an assertion rather than a factory. Reserve it for programmer-error
1165
1273
  * contexts where invalidity is a bug.
1166
1274
  *
1275
+ * Intake NARROWS, and transfers no ownership. What comes back is the caller's own object,
1276
+ * so a member carried on an accessor can answer this guard one way and a later reader
1277
+ * another. The division is deliberate: the borrowed-engine law governs values this package
1278
+ * pulls across a seam it called, and a value handed in at the door stays the caller's.
1279
+ * `snapshotBrief` is the ownership door, and `pinBrief`, `BriefManager`, `briefToMarkdown`,
1280
+ * `briefToGoal`, and `briefToDispatch` take it. `briefToSubject`, `briefToContent`, and
1281
+ * `briefToTrace` read the value they are handed instead, so a caller reaching one of those
1282
+ * directly owns that reading. Pass `assertBrief` a value you already own.
1283
+ *
1167
1284
  * @param data - The candidate brief data.
1168
1285
  * @returns The same value, now known to satisfy {@link Brief}.
1169
1286
  * @throws {@link BriefError} `INVALID` when `data` fails `isBrief`.
@@ -1503,8 +1620,10 @@ function deriveStatement(text) {
1503
1620
  * ```
1504
1621
  */
1505
1622
  function deriveTask(intent, text, actions, domains) {
1506
- const operation = Object.hasOwn(actions, intent.action) ? actions[intent.action] : void 0;
1507
- const domain = Object.hasOwn(domains, intent.domain) ? domains[intent.domain] : void 0;
1623
+ const operationDescriptor = Object.getOwnPropertyDescriptor(actions, intent.action);
1624
+ const domainDescriptor = Object.getOwnPropertyDescriptor(domains, intent.domain);
1625
+ const operation = operationDescriptor === void 0 ? void 0 : "value" in operationDescriptor ? operationDescriptor.value : operationDescriptor.get === void 0 ? void 0 : Reflect.apply(operationDescriptor.get, actions, []);
1626
+ const domain = domainDescriptor === void 0 ? void 0 : "value" in domainDescriptor ? domainDescriptor.value : domainDescriptor.get === void 0 ? void 0 : Reflect.apply(domainDescriptor.get, domains, []);
1508
1627
  if (!isTaskOperation(operation) || !isTaskDomain(domain)) return void 0;
1509
1628
  const statement = deriveStatement(text);
1510
1629
  return statement.length === 0 ? void 0 : task(operation, domain, statement);
@@ -1570,6 +1689,15 @@ function deriveGaps(ambiguities) {
1570
1689
  * all fail the same way — `undefined`, never a throw. Coerce a bare vocabulary value with
1571
1690
  * `parseEnum` from `@orkestrel/contract` against the exported tuple instead.
1572
1691
  *
1692
+ * The half of the intake pair that is OWNED BY CONSTRUCTION, which is what separates it from
1693
+ * `assertBrief`. The argument is text, so the graph the guard reads is one `JSON.parse` built
1694
+ * inside this call: it carries no caller identity, no accessor, and no alias back into anything
1695
+ * the caller still holds, and the parse-and-guard primitive this file imports from
1696
+ * `@orkestrel/contract` returns that same parsed graph rather than a second reading of it.
1697
+ * Every member `isBrief` checked therefore answers a later reader identically. The value is
1698
+ * fresh rather than frozen, so the caller owns it outright — reach for `snapshotBrief` when the
1699
+ * value came from code instead of from text.
1700
+ *
1573
1701
  * @param value - The JSON text to parse.
1574
1702
  * @returns The `Brief` when the parsed value satisfies `isBrief`, otherwise `undefined`.
1575
1703
  *
@@ -1880,7 +2008,15 @@ var BriefCompiler = class {
1880
2008
  }
1881
2009
  gate(source) {
1882
2010
  this.#refuseDestroyed();
1883
- const ruled = (0, _orkestrel_contract.attempt)(() => this.#own(this.#reason.reason(briefToSubject(source), gateDefinition())));
2011
+ const ruled = (0, _orkestrel_contract.attempt)(() => this.#own(this.#reason.reason(briefToSubject(source), gateDefinition()), [
2012
+ "reasoning",
2013
+ "conclusion",
2014
+ "rules",
2015
+ "count",
2016
+ "success",
2017
+ "trace",
2018
+ "errors"
2019
+ ]));
1884
2020
  if (!ruled.success) throw new BriefError("GATE_FAILED", errorToMessage(ruled.error), {
1885
2021
  stage: "gate",
1886
2022
  field: "reason"
@@ -1903,14 +2039,14 @@ var BriefCompiler = class {
1903
2039
  #snapshot(input) {
1904
2040
  return freezeDeep(structuredClone(input));
1905
2041
  }
1906
- #own(value) {
2042
+ #own(value, members) {
1907
2043
  const cloned = (0, _orkestrel_contract.attempt)(() => structuredClone(value));
1908
- return freezeDeep(cloned.success ? cloned.value : value);
2044
+ return cloned.success ? freezeDeep(cloned.value) : captureValue(value, members);
1909
2045
  }
1910
2046
  #read(input, raw, stages, failures) {
1911
2047
  const text = input.text;
1912
2048
  if (text !== void 0) {
1913
- const read = (0, _orkestrel_contract.attempt)(() => this.#own(this.#interpret.interpret(text)));
2049
+ const read = (0, _orkestrel_contract.attempt)(() => this.#own(this.#interpret.interpret(text), INTERPRETATION_MEMBERS));
1914
2050
  if (read.success && (0, _orkestrel_interpret.isInterpretation)(read.value)) {
1915
2051
  stages.push(Object.freeze({
1916
2052
  stage: "interpret",
@@ -1935,7 +2071,8 @@ var BriefCompiler = class {
1935
2071
  const supplied = input.interpretation;
1936
2072
  if (supplied === void 0 || (0, _orkestrel_interpret.isInterpretation)(supplied)) return supplied;
1937
2073
  const live = raw.interpretation;
1938
- if (live !== void 0 && (0, _orkestrel_interpret.isInterpretation)(live)) return freezeDeep(live);
2074
+ const captured = (0, _orkestrel_contract.attempt)(() => captureValue(live, INTERPRETATION_MEMBERS));
2075
+ if (captured.success && (0, _orkestrel_interpret.isInterpretation)(captured.value)) return captured.value;
1939
2076
  const message = "The supplied interpretation does not satisfy the published shape";
1940
2077
  stages.push(Object.freeze({
1941
2078
  stage: "interpret",
@@ -1959,7 +2096,7 @@ var BriefCompiler = class {
1959
2096
  code: "BLOCKED",
1960
2097
  message: `Gate refused: ${unready.join(", ")}`
1961
2098
  };
1962
- if (!(0, _orkestrel_reason.isLogicalResult)(verdict)) return void 0;
2099
+ if (verdict === void 0) return void 0;
1963
2100
  const refused = verdict.rules.filter((entry) => !entry.conclusion).map((entry) => entry.id).join(", ");
1964
2101
  if (refused.length === 0) return {
1965
2102
  stage: "gate",
@@ -2099,6 +2236,7 @@ exports.BriefError = BriefError;
2099
2236
  exports.BriefManager = BriefManager;
2100
2237
  exports.DEFAULT_BRIEF_TURNS = DEFAULT_BRIEF_TURNS;
2101
2238
  exports.GATE_ID = GATE_ID;
2239
+ exports.INTERPRETATION_MEMBERS = INTERPRETATION_MEMBERS;
2102
2240
  exports.LINE_BREAK_PATTERN = LINE_BREAK_PATTERN;
2103
2241
  exports.OUTPUT_FORMATS = OUTPUT_FORMATS;
2104
2242
  exports.RISK_SEVERITIES = RISK_SEVERITIES;
@@ -2115,6 +2253,7 @@ exports.briefToHash = briefToHash;
2115
2253
  exports.briefToMarkdown = briefToMarkdown;
2116
2254
  exports.briefToSubject = briefToSubject;
2117
2255
  exports.briefToTrace = briefToTrace;
2256
+ exports.captureValue = captureValue;
2118
2257
  exports.citation = citation;
2119
2258
  exports.citationShape = citationShape;
2120
2259
  exports.countSentences = countSentences;