@orkestrel/brief 0.0.4 → 0.0.5

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.
@@ -370,6 +370,84 @@ var isBrief = (0, _orkestrel_contract.recordOf)({
370
370
  //#endregion
371
371
  //#region src/core/cloners.ts
372
372
  /**
373
+ * Captures one stable, frozen view of a foreign contract value.
374
+ *
375
+ * @remarks
376
+ * Rebuilds the root and every reachable plain container from its own enumerable members.
377
+ * Unknown own members survive. Each published member absent from that copied own set is read
378
+ * once and materialized, which admits a class that supplies its contract through prototype
379
+ * accessors without leaving later reads attached to the live instance. Non-container leaves
380
+ * retain their identity, including functions that `structuredClone` cannot carry.
381
+ *
382
+ * @param source - The foreign value to capture.
383
+ * @param members - The published root member names to materialize when absent from its own set.
384
+ * @returns A deeply frozen plain view, or `source` itself when it is a primitive.
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * import { captureValue } from '@orkestrel/brief'
389
+ *
390
+ * const leaf = () => 'ready'
391
+ * const owned = captureValue({ leaf }, ['leaf'])
392
+ * Reflect.get(owned, 'leaf') === leaf // true — an uncloneable leaf keeps its identity
393
+ * Object.isFrozen(owned) // true
394
+ * ```
395
+ */
396
+ function captureValue(source, members) {
397
+ if (source === null || typeof source !== "object" && typeof source !== "function") return source;
398
+ const target = Array.isArray(source) ? [] : Object.create(null);
399
+ const seen = new WeakMap([[source, target]]);
400
+ const captured = [target];
401
+ const pending = [[
402
+ source,
403
+ target,
404
+ members
405
+ ]];
406
+ while (pending.length > 0) {
407
+ const frame = pending.pop();
408
+ if (frame === void 0) continue;
409
+ const [current, view, expected] = frame;
410
+ const entries = [];
411
+ const copied = /* @__PURE__ */ new Set();
412
+ for (const key of Reflect.ownKeys(current)) {
413
+ const descriptor = Reflect.getOwnPropertyDescriptor(current, key);
414
+ if (descriptor === void 0 || !descriptor.enumerable) continue;
415
+ copied.add(key);
416
+ entries.push([key, "value" in descriptor ? descriptor.value : Reflect.get(current, key)]);
417
+ }
418
+ for (const key of expected ?? []) if (!copied.has(key)) entries.push([key, Reflect.get(current, key)]);
419
+ for (const [key, value] of entries) {
420
+ let owned = value;
421
+ if (value !== null && typeof value === "object") {
422
+ const existing = seen.get(value);
423
+ if (existing !== void 0) owned = existing;
424
+ else {
425
+ const prototype = Reflect.getPrototypeOf(value);
426
+ if (Array.isArray(value) || prototype === null || prototype === Object.prototype) {
427
+ const branch = Array.isArray(value) ? [] : Object.create(null);
428
+ seen.set(value, branch);
429
+ captured.push(branch);
430
+ pending.push([
431
+ value,
432
+ branch,
433
+ void 0
434
+ ]);
435
+ owned = branch;
436
+ }
437
+ }
438
+ }
439
+ Reflect.defineProperty(view, key, {
440
+ value: owned,
441
+ enumerable: true,
442
+ configurable: false,
443
+ writable: false
444
+ });
445
+ }
446
+ }
447
+ for (const view of captured) Object.freeze(view);
448
+ return target;
449
+ }
450
+ /**
373
451
  * Return a deeply owned, deeply frozen copy of a brief, refusing anything off-contract.
374
452
  *
375
453
  * @remarks
@@ -1164,6 +1242,15 @@ function errorToMessage(error) {
1164
1242
  * nothing, so it is an assertion rather than a factory. Reserve it for programmer-error
1165
1243
  * contexts where invalidity is a bug.
1166
1244
  *
1245
+ * Intake NARROWS, and transfers no ownership. What comes back is the caller's own object,
1246
+ * so a member carried on an accessor can answer this guard one way and a later reader
1247
+ * another. The division is deliberate: the borrowed-engine law governs values this package
1248
+ * pulls across a seam it called, and a value handed in at the door stays the caller's.
1249
+ * `snapshotBrief` is the ownership door, and `pinBrief`, `BriefManager`, `briefToMarkdown`,
1250
+ * `briefToGoal`, and `briefToDispatch` take it. `briefToSubject`, `briefToContent`, and
1251
+ * `briefToTrace` read the value they are handed instead, so a caller reaching one of those
1252
+ * directly owns that reading. Pass `assertBrief` a value you already own.
1253
+ *
1167
1254
  * @param data - The candidate brief data.
1168
1255
  * @returns The same value, now known to satisfy {@link Brief}.
1169
1256
  * @throws {@link BriefError} `INVALID` when `data` fails `isBrief`.
@@ -1503,8 +1590,10 @@ function deriveStatement(text) {
1503
1590
  * ```
1504
1591
  */
1505
1592
  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;
1593
+ const operationDescriptor = Object.getOwnPropertyDescriptor(actions, intent.action);
1594
+ const domainDescriptor = Object.getOwnPropertyDescriptor(domains, intent.domain);
1595
+ const operation = operationDescriptor === void 0 ? void 0 : "value" in operationDescriptor ? operationDescriptor.value : operationDescriptor.get === void 0 ? void 0 : Reflect.apply(operationDescriptor.get, actions, []);
1596
+ const domain = domainDescriptor === void 0 ? void 0 : "value" in domainDescriptor ? domainDescriptor.value : domainDescriptor.get === void 0 ? void 0 : Reflect.apply(domainDescriptor.get, domains, []);
1508
1597
  if (!isTaskOperation(operation) || !isTaskDomain(domain)) return void 0;
1509
1598
  const statement = deriveStatement(text);
1510
1599
  return statement.length === 0 ? void 0 : task(operation, domain, statement);
@@ -1570,6 +1659,15 @@ function deriveGaps(ambiguities) {
1570
1659
  * all fail the same way — `undefined`, never a throw. Coerce a bare vocabulary value with
1571
1660
  * `parseEnum` from `@orkestrel/contract` against the exported tuple instead.
1572
1661
  *
1662
+ * The half of the intake pair that is OWNED BY CONSTRUCTION, which is what separates it from
1663
+ * `assertBrief`. The argument is text, so the graph the guard reads is one `JSON.parse` built
1664
+ * inside this call: it carries no caller identity, no accessor, and no alias back into anything
1665
+ * the caller still holds, and the parse-and-guard primitive this file imports from
1666
+ * `@orkestrel/contract` returns that same parsed graph rather than a second reading of it.
1667
+ * Every member `isBrief` checked therefore answers a later reader identically. The value is
1668
+ * fresh rather than frozen, so the caller owns it outright — reach for `snapshotBrief` when the
1669
+ * value came from code instead of from text.
1670
+ *
1573
1671
  * @param value - The JSON text to parse.
1574
1672
  * @returns The `Brief` when the parsed value satisfies `isBrief`, otherwise `undefined`.
1575
1673
  *
@@ -1880,7 +1978,15 @@ var BriefCompiler = class {
1880
1978
  }
1881
1979
  gate(source) {
1882
1980
  this.#refuseDestroyed();
1883
- const ruled = (0, _orkestrel_contract.attempt)(() => this.#own(this.#reason.reason(briefToSubject(source), gateDefinition())));
1981
+ const ruled = (0, _orkestrel_contract.attempt)(() => this.#own(this.#reason.reason(briefToSubject(source), gateDefinition()), [
1982
+ "reasoning",
1983
+ "conclusion",
1984
+ "rules",
1985
+ "count",
1986
+ "success",
1987
+ "trace",
1988
+ "errors"
1989
+ ]));
1884
1990
  if (!ruled.success) throw new BriefError("GATE_FAILED", errorToMessage(ruled.error), {
1885
1991
  stage: "gate",
1886
1992
  field: "reason"
@@ -1903,14 +2009,30 @@ var BriefCompiler = class {
1903
2009
  #snapshot(input) {
1904
2010
  return freezeDeep(structuredClone(input));
1905
2011
  }
1906
- #own(value) {
2012
+ #own(value, members) {
1907
2013
  const cloned = (0, _orkestrel_contract.attempt)(() => structuredClone(value));
1908
- return freezeDeep(cloned.success ? cloned.value : value);
2014
+ return cloned.success ? freezeDeep(cloned.value) : captureValue(value, members);
1909
2015
  }
1910
2016
  #read(input, raw, stages, failures) {
2017
+ const members = [
2018
+ "text",
2019
+ "normalized",
2020
+ "intent",
2021
+ "entities",
2022
+ "subject",
2023
+ "definition",
2024
+ "mappings",
2025
+ "ambiguities",
2026
+ "prompt",
2027
+ "stages",
2028
+ "failures",
2029
+ "complete",
2030
+ "confidence",
2031
+ "digest"
2032
+ ];
1911
2033
  const text = input.text;
1912
2034
  if (text !== void 0) {
1913
- const read = (0, _orkestrel_contract.attempt)(() => this.#own(this.#interpret.interpret(text)));
2035
+ const read = (0, _orkestrel_contract.attempt)(() => this.#own(this.#interpret.interpret(text), members));
1914
2036
  if (read.success && (0, _orkestrel_interpret.isInterpretation)(read.value)) {
1915
2037
  stages.push(Object.freeze({
1916
2038
  stage: "interpret",
@@ -1935,7 +2057,8 @@ var BriefCompiler = class {
1935
2057
  const supplied = input.interpretation;
1936
2058
  if (supplied === void 0 || (0, _orkestrel_interpret.isInterpretation)(supplied)) return supplied;
1937
2059
  const live = raw.interpretation;
1938
- if (live !== void 0 && (0, _orkestrel_interpret.isInterpretation)(live)) return freezeDeep(live);
2060
+ const captured = (0, _orkestrel_contract.attempt)(() => captureValue(live, members));
2061
+ if (captured.success && (0, _orkestrel_interpret.isInterpretation)(captured.value)) return captured.value;
1939
2062
  const message = "The supplied interpretation does not satisfy the published shape";
1940
2063
  stages.push(Object.freeze({
1941
2064
  stage: "interpret",
@@ -1959,7 +2082,7 @@ var BriefCompiler = class {
1959
2082
  code: "BLOCKED",
1960
2083
  message: `Gate refused: ${unready.join(", ")}`
1961
2084
  };
1962
- if (!(0, _orkestrel_reason.isLogicalResult)(verdict)) return void 0;
2085
+ if (verdict === void 0) return void 0;
1963
2086
  const refused = verdict.rules.filter((entry) => !entry.conclusion).map((entry) => entry.id).join(", ");
1964
2087
  if (refused.length === 0) return {
1965
2088
  stage: "gate",
@@ -2115,6 +2238,7 @@ exports.briefToHash = briefToHash;
2115
2238
  exports.briefToMarkdown = briefToMarkdown;
2116
2239
  exports.briefToSubject = briefToSubject;
2117
2240
  exports.briefToTrace = briefToTrace;
2241
+ exports.captureValue = captureValue;
2118
2242
  exports.citation = citation;
2119
2243
  exports.citationShape = citationShape;
2120
2244
  exports.countSentences = countSentences;