@mindot/will 0.6.0 → 0.7.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.
@@ -1,4 +1,4 @@
1
- import { W as Will } from '../will-Bikuk4s2.js';
1
+ import { W as Will } from '../will-DAW0l-lY.js';
2
2
  import { C as ChannelBridge } from '../types-E9-HV-SW.js';
3
3
 
4
4
  interface DiscordLikeChannel {
@@ -1,4 +1,4 @@
1
- import { W as Will } from '../will-Bikuk4s2.js';
1
+ import { W as Will } from '../will-DAW0l-lY.js';
2
2
  import { C as ChannelBridge } from '../types-E9-HV-SW.js';
3
3
 
4
4
  interface WaLikeMessage {
package/dist/cli.js CHANGED
@@ -3625,11 +3625,19 @@ var PROC_THRESHOLD = 0.6;
3625
3625
  var IDLE_TICKS = 200;
3626
3626
  var DECAY_RATE = 0.02;
3627
3627
  var DROP_HABIT = 0.05;
3628
+ var AVAIL_DROP_CLASS = 0.5;
3629
+ var AVAIL_DROP_INSTANCE = 0.12;
3630
+ var AVAIL_FLOOR = 0.05;
3631
+ var AVAIL_RECOVERY = 0.02;
3632
+ var AVAIL_RECOVERED = 0.999;
3628
3633
  var SchemaRepertoire = class {
3629
3634
  _templates = /* @__PURE__ */ new Map();
3630
3635
  _skills = /* @__PURE__ */ new Map();
3631
3636
  /** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
3632
3637
  _learned = /* @__PURE__ */ new Set();
3638
+ /** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
3639
+ * a refusal lands — a never-refused Will writes nothing here (byte-identical). */
3640
+ _availability = /* @__PURE__ */ new Map();
3633
3641
  constructor(seed = INNATE_SCHEMAS) {
3634
3642
  for (const s of seed) this._templates.set(s.id, s);
3635
3643
  }
@@ -3663,6 +3671,31 @@ var SchemaRepertoire = class {
3663
3671
  getSkill(id) {
3664
3672
  return this._skills.get(id);
3665
3673
  }
3674
+ // ── availability (P2) ─────────────────────────────────────────
3675
+ availability() {
3676
+ return this._availability;
3677
+ }
3678
+ /**
3679
+ * How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
3680
+ * (fully available — the common case). This is the ONLY value the
3681
+ * AffordanceSynthesizer reads; it never touches competence.
3682
+ */
3683
+ availabilityOf(schema) {
3684
+ return this._availability.get(schema)?.value ?? 1;
3685
+ }
3686
+ /**
3687
+ * Fold a policy refusal into the availability layer (NOT competence). A
3688
+ * `class` refusal cuts availability hard; an `instance` refusal dents it
3689
+ * lightly. Multiplicative so repeated refusals compound toward — but never
3690
+ * reach — zero, keeping re-probe alive.
3691
+ */
3692
+ recordRefusal(schema, finality, tick) {
3693
+ const prev = this._availability.get(schema)?.value ?? 1;
3694
+ const drop = finality === "class" ? AVAIL_DROP_CLASS : AVAIL_DROP_INSTANCE;
3695
+ const value = Math.max(AVAIL_FLOOR, prev * (1 - drop));
3696
+ this._availability.set(schema, { value, lastRefusedTick: tick });
3697
+ return value;
3698
+ }
3666
3699
  /**
3667
3700
  * Fold one outcome into the schema's learned skill. Returns the updated skill
3668
3701
  * and whether it just crossed the proceduralization threshold this update.
@@ -3689,12 +3722,14 @@ var SchemaRepertoire = class {
3689
3722
  return { skill, proceduralized: !wasProceduralized && habitStrength >= PROC_THRESHOLD };
3690
3723
  }
3691
3724
  /**
3692
- * Forgetting curve over the competence layer. Skills unused for IDLE_TICKS
3693
- * lose habit; learned composites that fall below DROP_HABIT are dropped
3694
- * entirely (template + skill). Returns the schema ids that were forgotten.
3725
+ * Forgetting curve over the competence layer, plus availability recovery.
3726
+ * Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
3727
+ * are dropped entirely (template + skill). Availability entries climb back
3728
+ * toward 1 and are dropped once fully recovered. Returns the ids that were
3729
+ * removed from each layer so their mirrored state entities can be deleted.
3695
3730
  */
3696
3731
  decay(tick) {
3697
- const dropped = [];
3732
+ const skills = [];
3698
3733
  for (const [id, skill] of this._skills) {
3699
3734
  if (tick - skill.lastEnactedTick <= IDLE_TICKS) continue;
3700
3735
  const habitStrength = clamp01(skill.habitStrength - DECAY_RATE);
@@ -3702,12 +3737,20 @@ var SchemaRepertoire = class {
3702
3737
  this._skills.delete(id);
3703
3738
  this._templates.delete(id);
3704
3739
  this._learned.delete(id);
3705
- dropped.push(id);
3740
+ skills.push(id);
3706
3741
  continue;
3707
3742
  }
3708
3743
  this._skills.set(id, { ...skill, habitStrength });
3709
3744
  }
3710
- return dropped;
3745
+ const availability = [];
3746
+ for (const [id, avail] of this._availability) {
3747
+ const value = avail.value + AVAIL_RECOVERY * (1 - avail.value);
3748
+ if (value >= AVAIL_RECOVERED) {
3749
+ this._availability.delete(id);
3750
+ availability.push(id);
3751
+ } else this._availability.set(id, { ...avail, value });
3752
+ }
3753
+ return { skills, availability };
3711
3754
  }
3712
3755
  // ── PMA portability (Phase 6 reads these) ─────────────────────
3713
3756
  /** Learned composite templates + all skills above a confidence floor. */
@@ -3756,6 +3799,28 @@ var SchemaRepertoire = class {
3756
3799
  this._learned.add(s.id);
3757
3800
  }
3758
3801
  }
3802
+ /** Availability ledger encoded as `agency.availability` state entities (P2).
3803
+ * Empty until a refusal lands, so the quiet path writes nothing. */
3804
+ availabilityEntities() {
3805
+ const out = [];
3806
+ for (const [schema, a] of this._availability)
3807
+ out.push(availabilityEntity(schema, a.value, a.lastRefusedTick));
3808
+ return out;
3809
+ }
3810
+ /** Rehydrate the availability ledger from state after a restore. Idempotent;
3811
+ * keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
3812
+ restoreAvailability(entities) {
3813
+ for (const e of entities.values()) {
3814
+ if (e.type !== AVAILABILITY_ENTITY_TYPE) continue;
3815
+ const m = e.metadata ?? {};
3816
+ const schema = typeof m["schema"] === "string" ? m["schema"] : "";
3817
+ if (!schema) continue;
3818
+ const value = typeof m["value"] === "number" ? m["value"] : 1;
3819
+ const tick = typeof m["lastRefusedTick"] === "number" ? m["lastRefusedTick"] : 0;
3820
+ const prev = this._availability.get(schema);
3821
+ if (!prev || value < prev.value) this._availability.set(schema, { value, lastRefusedTick: tick });
3822
+ }
3823
+ }
3759
3824
  };
3760
3825
  function freshSkill(schema, value, tick) {
3761
3826
  return {
@@ -3772,6 +3837,17 @@ function freshSkill(schema, value, tick) {
3772
3837
  function clamp01(n) {
3773
3838
  return n < 0 ? 0 : n > 1 ? 1 : n;
3774
3839
  }
3840
+ var AVAILABILITY_ENTITY_TYPE = "agency.availability";
3841
+ function availabilityEntityId(schema) {
3842
+ return `agency-availability-${schema}`;
3843
+ }
3844
+ function availabilityEntity(schema, value, lastRefusedTick) {
3845
+ return {
3846
+ id: availabilityEntityId(schema),
3847
+ type: AVAILABILITY_ENTITY_TYPE,
3848
+ metadata: { schema, value, lastRefusedTick }
3849
+ };
3850
+ }
3775
3851
  var SCHEMA_ENTITY_TYPE = "agency.schema";
3776
3852
  function schemaEntityId(schemaId) {
3777
3853
  return `agency-schema-${schemaId}`;
@@ -20051,7 +20127,9 @@ function risk(a, bias) {
20051
20127
  return clamp016(Math.max(0, -a.expectedValence) * 0.5 + bias.threat * 0.5);
20052
20128
  }
20053
20129
  function scoreAffordance(a, bias, w = DEFAULT_WEIGHTS) {
20054
- return w.goal * goalRelevance(a, bias) + w.reward * a.expectedReward + w.novelty * novelty(a) + w.drive * driveUrgency(a, bias) + w.habit * a.habitStrength + w.plan * (a.planBias ?? 0) - w.cost * a.cost - w.inhib * bias.inhibition - w.risk * risk(a, bias);
20130
+ const raw = w.goal * goalRelevance(a, bias) + w.reward * a.expectedReward + w.novelty * novelty(a) + w.drive * driveUrgency(a, bias) + w.habit * a.habitStrength + w.plan * (a.planBias ?? 0) - w.cost * a.cost - w.inhib * bias.inhibition - w.risk * risk(a, bias);
20131
+ const availability = a.availability ?? 1;
20132
+ return raw > 0 ? raw * availability : raw;
20055
20133
  }
20056
20134
  function stakes(winner, bias) {
20057
20135
  return clamp016(Math.max(
@@ -20130,6 +20208,7 @@ var AffordanceSynthesizer = class {
20130
20208
  // ── react ─────────────────────────────────────────────────────
20131
20209
  async react(_delta, tick, state, _context) {
20132
20210
  this._repertoire?.restoreComposites(state.entities);
20211
+ this._repertoire?.restoreAvailability(state.entities);
20133
20212
  const schemas = this._repertoire?.schemas() ?? this._schemas;
20134
20213
  const skills = this._skills?.() ?? this._repertoire?.skills() ?? null;
20135
20214
  const valence = metric(state, "affect.valence", 0);
@@ -20255,6 +20334,7 @@ var AffordanceSynthesizer = class {
20255
20334
  /** Compose an Affordance from a schema + the evoking context, folding in learned priors. */
20256
20335
  _build(schema, tick, state, valence, energyLow, skills, ctx) {
20257
20336
  const skill = skills?.get(schema.id);
20337
+ const availability = this._repertoire?.availabilityOf(schema.id) ?? 1;
20258
20338
  const expectedReward = skill?.valueEstimate ?? clamp017(((schema.baseValence ?? 0) + 1) / 2);
20259
20339
  const expectedValence = schema.baseValence ?? valence;
20260
20340
  const habitStrength = skill?.habitStrength ?? 0;
@@ -20276,6 +20356,7 @@ var AffordanceSynthesizer = class {
20276
20356
  available: this._available(schema.preconditions, (k) => metric(state, k, 0)),
20277
20357
  tags: schema.tags ?? [],
20278
20358
  ...schema.description ? { description: schema.description } : {},
20359
+ ...availability < 1 ? { availability } : {},
20279
20360
  planBias: ctx.planBias,
20280
20361
  planId: ctx.planId,
20281
20362
  stepId: ctx.stepId,
@@ -20299,6 +20380,7 @@ var AffordanceSynthesizer = class {
20299
20380
  available: a.available,
20300
20381
  tags: a.tags,
20301
20382
  description: a.description,
20383
+ ...a.availability !== void 0 ? { availability: a.availability } : {},
20302
20384
  planBias: a.planBias,
20303
20385
  planId: a.planId,
20304
20386
  stepId: a.stepId,
@@ -20502,7 +20584,10 @@ var ActionSelector = class {
20502
20584
  ]
20503
20585
  }
20504
20586
  });
20505
- if (deliberating && rupture >= RUPTURE_REVOKE_GATE) {
20587
+ const policyRevoke = !!deliberating && refusedClassSchemas(state).has(deliberating.schema);
20588
+ if (deliberating && (rupture >= RUPTURE_REVOKE_GATE || policyRevoke)) {
20589
+ const reason = policyRevoke ? "policy-refusal" : "exafferent-rupture";
20590
+ const revRupture = policyRevoke ? Math.max(rupture, RUPTURE_REVOKE_GATE) : rupture;
20506
20591
  if (this._bus) {
20507
20592
  try {
20508
20593
  this._bus.publish({
@@ -20510,7 +20595,7 @@ var ActionSelector = class {
20510
20595
  version: 1,
20511
20596
  sourceEngine: this.name,
20512
20597
  salience: 0.85,
20513
- payload: { from: deliberating.schema, reason: "exafferent-rupture", rupture, tick }
20598
+ payload: { from: deliberating.schema, reason, rupture: revRupture, tick }
20514
20599
  });
20515
20600
  } catch (err) {
20516
20601
  logger.warn(`[selector] revoked publish failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -20519,11 +20604,12 @@ var ActionSelector = class {
20519
20604
  this._lastRevoked = { schema: deliberating.schema, tick };
20520
20605
  return {
20521
20606
  commands: {
20522
- set: [revocationEntity(deliberating.id, deliberating.schema, rupture, tick)],
20607
+ set: [revocationEntity(deliberating.id, deliberating.schema, revRupture, tick)],
20523
20608
  metrics: [
20524
20609
  ["agency.field.eligible", eligible.length],
20525
20610
  ["agency.selection.busy", 1],
20526
20611
  ["agency.commitment.revoked", 1],
20612
+ ...policyRevoke ? [["agency.policy.revoked", 1]] : [],
20527
20613
  ...stabMetrics
20528
20614
  ]
20529
20615
  }
@@ -20734,6 +20820,17 @@ function computeRupture(state, tick, senseEvents = []) {
20734
20820
  if (maxSalience <= RUPTURE_SALIENCE_GATE) return 0;
20735
20821
  return clamp018((maxSalience - RUPTURE_SALIENCE_GATE) / (1 - RUPTURE_SALIENCE_GATE));
20736
20822
  }
20823
+ function refusedClassSchemas(state) {
20824
+ const out = /* @__PURE__ */ new Set();
20825
+ for (const e of state.entities.values()) {
20826
+ if (e.type !== "agency.outcome") continue;
20827
+ const m = e.metadata;
20828
+ if (m?.["refused"] !== true || str2(m?.["finality"]) !== "class") continue;
20829
+ const schema = str2(m?.["schema"]);
20830
+ if (schema) out.add(schema);
20831
+ }
20832
+ return out;
20833
+ }
20737
20834
  function effectiveWeights(state) {
20738
20835
  const p = readEffectiveParams(state, "engine-config-action-selector");
20739
20836
  return {
@@ -21121,6 +21218,7 @@ var MotorSchemaExecutor = class {
21121
21218
  }
21122
21219
  for (const [id, e] of state.entities) {
21123
21220
  if (e.type !== "agency.intent" || str5(e.metadata?.["status"]) !== "awaiting") continue;
21221
+ if (e.metadata?.["escalated"] === true) continue;
21124
21222
  const dispatchedAt = num3(e.metadata?.["dispatchedAt"], tick);
21125
21223
  if (tick - dispatchedAt < AWAIT_TIMEOUT) continue;
21126
21224
  const intent = readIntent(id, e.metadata);
@@ -21631,12 +21729,24 @@ var ReafferenceEngine = class {
21631
21729
  }
21632
21730
  let updates = 0;
21633
21731
  let discovered = 0;
21732
+ let refused = 0;
21634
21733
  for (const { id, meta: m, fromState } of outcomes) {
21635
21734
  const schema = str6(m["schema"]);
21636
21735
  if (!schema) {
21637
21736
  if (fromState) del.push(id);
21638
21737
  continue;
21639
21738
  }
21739
+ if (m["refused"] === true) {
21740
+ const finality = str6(m["finality"]) === "class" ? "class" : "instance";
21741
+ this._repertoire.recordRefusal(schema, finality, tick);
21742
+ if (fromState) del.push(id);
21743
+ const refusedIntent = str6(m["intentId"]);
21744
+ if (refusedIntent) del.push(refusedIntent);
21745
+ const refusedPlan = str6(m["planId"]);
21746
+ if (refusedPlan) this._emitPlanOutcome(refusedPlan, str6(m["stepId"]), schema, false, 0, 0, tick);
21747
+ refused++;
21748
+ continue;
21749
+ }
21640
21750
  const { skill, proceduralized } = this._repertoire.recordOutcome({
21641
21751
  schema,
21642
21752
  success: m["success"] === true,
@@ -21663,11 +21773,14 @@ var ReafferenceEngine = class {
21663
21773
  }
21664
21774
  }
21665
21775
  const dropped = this._repertoire.decay(tick);
21666
- for (const id of dropped) {
21776
+ for (const id of dropped.skills) {
21667
21777
  del.push(`agency-skill-${id}`);
21668
21778
  del.push(schemaEntityId(id));
21669
21779
  }
21780
+ for (const id of dropped.availability)
21781
+ del.push(availabilityEntityId(id));
21670
21782
  for (const e of this._repertoire.compositeEntities()) set.push(e);
21783
+ for (const e of this._repertoire.availabilityEntities()) set.push(e);
21671
21784
  const skills = this._repertoire.skills();
21672
21785
  const habitual = [...skills.values()].filter((s) => s.habitStrength >= PROC_THRESHOLD2).length;
21673
21786
  metrics.push(
@@ -21677,6 +21790,7 @@ var ReafferenceEngine = class {
21677
21790
  ["agency.habitual.count", habitual],
21678
21791
  ["agency.sensory.confirmed", sensory]
21679
21792
  );
21793
+ if (refused > 0) metrics.push(["agency.refused.count", refused]);
21680
21794
  return { commands: { set, delete: del, metrics } };
21681
21795
  }
21682
21796
  _emitProceduralized(skill, tick) {
@@ -24915,6 +25029,7 @@ function reconcileInvocation(intentId, schema, result, tick, predicted = { rewar
24915
25029
  mode: "external",
24916
25030
  tick,
24917
25031
  reconciled: true,
25032
+ ...result.refused ? { refused: true, finality: result.finality ?? "instance" } : {},
24918
25033
  ...provenance.planId ? { planId: provenance.planId } : {},
24919
25034
  ...provenance.stepId ? { stepId: provenance.stepId } : {}
24920
25035
  }
@@ -24924,8 +25039,56 @@ function clamp0112(n) {
24924
25039
  return n < 0 ? 0 : n > 1 ? 1 : n;
24925
25040
  }
24926
25041
 
25042
+ // src/stem/policy/arbiter.ts
25043
+ var ALLOW = Object.freeze({ decision: "allow" });
25044
+ var NULL_ARBITER = {
25045
+ name: "null",
25046
+ evaluate() {
25047
+ return ALLOW;
25048
+ }
25049
+ };
25050
+ function isNullArbiter(arbiter) {
25051
+ return !arbiter || arbiter === NULL_ARBITER;
25052
+ }
25053
+
25054
+ // src/stem/policy/verdict.recorder.ts
25055
+ var _sinks3 = /* @__PURE__ */ new Map();
25056
+ function getVerdictRecorder(willId) {
25057
+ return _sinks3.get(willId);
25058
+ }
25059
+ var _sources3 = /* @__PURE__ */ new Map();
25060
+ function getVerdictSource(willId) {
25061
+ return _sources3.get(willId);
25062
+ }
25063
+
24927
25064
  // src/stem/tracts/effector.controller.ts
25065
+ var ESCALATION_TTL_TICKS = 30;
24928
25066
  var effectorController = class {
25067
+ /** The Policy Decision Point consulted before an invocation reaches the world.
25068
+ * Defaults to the no-op arbiter, so an unconfigured Will is byte-identical. */
25069
+ _arbiter = NULL_ARBITER;
25070
+ /**
25071
+ * Denials queued during a step's flush, drained at the NEXT tick boundary
25072
+ * (POLICY_REAFFERENCE P1). Keyed by willId — harness state, exactly like
25073
+ * `pendingEffectorInvocations`; never simulation state, so it does not touch
25074
+ * `simulation.step` determinism and is regenerated on any re-execution.
25075
+ */
25076
+ _pendingRefusals = /* @__PURE__ */ new Map();
25077
+ /** Escalations awaiting their first application (mark intent + voice the ask). */
25078
+ _newEscalations = /* @__PURE__ */ new Map();
25079
+ /** Escalations currently held, keyed by intent id — the resolvable set. */
25080
+ _activeEscalations = /* @__PURE__ */ new Map();
25081
+ /** Host answers awaiting application at the next tick boundary. */
25082
+ _pendingResolutions = /* @__PURE__ */ new Map();
25083
+ /**
25084
+ * Install a Policy Decision Point (POLICY_REAFFERENCE P0). Passing null
25085
+ * restores the no-op default. The arbiter sees only the proposed act — never
25086
+ * simulation state — and its verdict decides whether the invocation is
25087
+ * handed to the host at all.
25088
+ */
25089
+ setArbiter(arbiter) {
25090
+ this._arbiter = arbiter ?? NULL_ARBITER;
25091
+ }
24929
25092
  /**
24930
25093
  * Update the set of allowed communication effectors at runtime via AccessGrants
24931
25094
  * (the permission / sense gate the senses + reply path read).
@@ -24941,6 +25104,224 @@ var effectorController = class {
24941
25104
  * echoes it on its result-ack, and `confirmExecution` uses it to find the intent.
24942
25105
  */
24943
25106
  bufferInvocation(instance, payload) {
25107
+ const willId = instance.config.id;
25108
+ const source = getVerdictSource(willId);
25109
+ if (source) {
25110
+ const invocation2 = toPolicyInvocation(instance, payload);
25111
+ const record = source.verdictFor(invocation2.tick, invocation2.intentId);
25112
+ if (record) this._applyVerdict(instance, payload, invocation2, recordToVerdict(record));
25113
+ else this._buffer(instance, payload);
25114
+ return;
25115
+ }
25116
+ if (isNullArbiter(this._arbiter)) {
25117
+ this._buffer(instance, payload);
25118
+ return;
25119
+ }
25120
+ const invocation = toPolicyInvocation(instance, payload);
25121
+ let verdict;
25122
+ try {
25123
+ verdict = this._arbiter.evaluate(invocation);
25124
+ } catch (err) {
25125
+ logger.error(`[policy] arbiter "${this._arbiter.name}" threw for "${invocation.schema}" \u2014 failing closed:`, err);
25126
+ return;
25127
+ }
25128
+ if (verdict instanceof Promise) {
25129
+ void verdict.then(
25130
+ (v) => this._recordAndApply(instance, payload, invocation, v),
25131
+ (err) => logger.error(`[policy] arbiter "${this._arbiter.name}" rejected for "${invocation.schema}" \u2014 failing closed:`, err)
25132
+ );
25133
+ return;
25134
+ }
25135
+ this._recordAndApply(instance, payload, invocation, verdict);
25136
+ }
25137
+ /** Capture the verdict on the tape (if a recorder is attached), then enforce it. */
25138
+ _recordAndApply(instance, payload, invocation, verdict) {
25139
+ const sink = getVerdictRecorder(instance.config.id);
25140
+ sink?.recordVerdict({
25141
+ tick: invocation.tick,
25142
+ willId: instance.config.id,
25143
+ intentId: invocation.intentId,
25144
+ schema: invocation.schema,
25145
+ arbiter: this._arbiter.name,
25146
+ decision: verdict.decision,
25147
+ ...verdict.reasonCode ? { reasonCode: verdict.reasonCode } : {},
25148
+ ...verdict.finality ? { finality: verdict.finality } : {},
25149
+ ...verdict.counterfactual ? { counterfactual: verdict.counterfactual } : {},
25150
+ timestamp: Date.now()
25151
+ });
25152
+ this._applyVerdict(instance, payload, invocation, verdict);
25153
+ }
25154
+ /**
25155
+ * Enforce a verdict (POLICY_REAFFERENCE P1).
25156
+ *
25157
+ * • allow → hand the invocation to the world.
25158
+ * • deny → queue a refusal ack, applied at the next tick boundary via
25159
+ * `confirmExecution` — the same lifecycle as a host rejection,
25160
+ * so the mind meets *world resistance*, not a permission dialog.
25161
+ * • escalate → raise a held escalation (POLICY_REAFFERENCE P4): the intent is
25162
+ * held (the executor stops timing it out), the Will voices a
25163
+ * first-person ask once, and a host resolution later approves
25164
+ * (dispatch) or denies (refuse). Unresolved, it degrades to a
25165
+ * refusal at ESCALATION_TTL_TICKS.
25166
+ *
25167
+ * P1's refusal reconciles as a plain FAILURE — safe, but the wrong learning
25168
+ * signal (forbidden ≠ unskilled). P2 routes it to affordance AVAILABILITY
25169
+ * instead of competence.
25170
+ */
25171
+ _applyVerdict(instance, payload, invocation, verdict) {
25172
+ if (verdict.decision === "allow") {
25173
+ this._buffer(instance, payload);
25174
+ return;
25175
+ }
25176
+ const cf = verdict.counterfactual;
25177
+ logger.info(
25178
+ `[policy] ${verdict.decision.toUpperCase()} "${invocation.schema}" intent "${invocation.intentId}" \u2014 ${verdict.reasonCode ?? "no reason code"}` + (verdict.finality ? ` (${verdict.finality})` : "") + (cf ? ` [${cf.field}: requested ${JSON.stringify(cf.requested)}, allowed ${JSON.stringify(cf.allowed)}]` : "")
25179
+ );
25180
+ if (verdict.decision === "deny") {
25181
+ const queue = this._pendingRefusals.get(instance.config.id) ?? [];
25182
+ queue.push({
25183
+ intentId: invocation.intentId,
25184
+ schema: invocation.schema,
25185
+ reasonCode: verdict.reasonCode ?? "POLICY_DENIED",
25186
+ finality: verdict.finality ?? "instance"
25187
+ });
25188
+ this._pendingRefusals.set(instance.config.id, queue);
25189
+ return;
25190
+ }
25191
+ const escalations = this._newEscalations.get(instance.config.id) ?? [];
25192
+ escalations.push({
25193
+ intentId: invocation.intentId,
25194
+ schema: invocation.schema,
25195
+ reasonCode: verdict.reasonCode ?? "APPROVAL_REQUIRED",
25196
+ payload,
25197
+ expiresAt: 0
25198
+ // stamped when applied (we don't have the current tick here)
25199
+ });
25200
+ this._newEscalations.set(instance.config.id, escalations);
25201
+ }
25202
+ /**
25203
+ * Record a host's answer to an escalation (POLICY_REAFFERENCE P4). Applied at
25204
+ * the next tick boundary so every simulation-state write stays on the boundary:
25205
+ * approve dispatches the held invocation to the world; deny refuses it. A
25206
+ * no-op if the intent id is not (or no longer) an active escalation.
25207
+ */
25208
+ resolveEscalation(instance, intentId, approved) {
25209
+ const queue = this._pendingResolutions.get(instance.config.id) ?? [];
25210
+ queue.push({ intentId, approved });
25211
+ this._pendingResolutions.set(instance.config.id, queue);
25212
+ }
25213
+ /**
25214
+ * Apply queued policy refusals as failure acks (POLICY_REAFFERENCE P1).
25215
+ * Called by the tick loop at the same boundary as inbound acks — BEFORE the
25216
+ * step, stamped to this tick — so a denial reconciled here is the exact
25217
+ * lifecycle of a host rejection that arrived between ticks.
25218
+ */
25219
+ applyPolicyOutcomes(instance) {
25220
+ const tick = instance.tickCount;
25221
+ this._applyResolutions(instance);
25222
+ this._expireEscalations(instance, tick);
25223
+ this._applyNewEscalations(instance, tick);
25224
+ this._applyRefusals(instance);
25225
+ }
25226
+ /** Drain queued refusals into failure acks (POLICY_REAFFERENCE P1). */
25227
+ _applyRefusals(instance) {
25228
+ const queue = this._pendingRefusals.get(instance.config.id);
25229
+ if (!queue || queue.length === 0) return;
25230
+ this._pendingRefusals.set(instance.config.id, []);
25231
+ for (const refusal of queue)
25232
+ this.confirmExecution(instance, refusal.intentId, {
25233
+ success: false,
25234
+ refused: true,
25235
+ finality: refusal.finality === "class" ? "class" : "instance",
25236
+ description: `refused by policy: ${refusal.reasonCode} (${refusal.finality})`
25237
+ });
25238
+ }
25239
+ /** Raise each newly-escalated intent (POLICY_REAFFERENCE P4): mark it held in
25240
+ * simulation state, voice the ask ONCE, and move it to the resolvable set. */
25241
+ _applyNewEscalations(instance, tick) {
25242
+ const pending = this._newEscalations.get(instance.config.id);
25243
+ if (!pending || pending.length === 0) return;
25244
+ this._newEscalations.set(instance.config.id, []);
25245
+ const active = this._activeEscalations.get(instance.config.id) ?? /* @__PURE__ */ new Map();
25246
+ for (const esc of pending) {
25247
+ esc.expiresAt = tick + ESCALATION_TTL_TICKS;
25248
+ this._markEscalated(instance, esc.intentId, esc.expiresAt);
25249
+ this._voiceEscalation(instance, esc);
25250
+ active.set(esc.intentId, esc);
25251
+ }
25252
+ this._activeEscalations.set(instance.config.id, active);
25253
+ }
25254
+ /** Apply host answers to active escalations (POLICY_REAFFERENCE P4). */
25255
+ _applyResolutions(instance) {
25256
+ const queue = this._pendingResolutions.get(instance.config.id);
25257
+ if (!queue || queue.length === 0) return;
25258
+ this._pendingResolutions.set(instance.config.id, []);
25259
+ const active = this._activeEscalations.get(instance.config.id);
25260
+ for (const { intentId, approved } of queue) {
25261
+ const esc = active?.get(intentId);
25262
+ if (!esc) continue;
25263
+ active.delete(intentId);
25264
+ this._clearEscalated(instance, intentId);
25265
+ if (approved) {
25266
+ this._buffer(instance, esc.payload);
25267
+ logger.info(`[policy] escalation APPROVED \u2192 dispatching "${esc.schema}" intent "${intentId}"`);
25268
+ } else {
25269
+ this._queueRefusal(instance, esc.intentId, esc.schema, esc.reasonCode, "class");
25270
+ logger.info(`[policy] escalation DENIED \u2192 refusing "${esc.schema}" intent "${intentId}"`);
25271
+ }
25272
+ }
25273
+ }
25274
+ /** Degrade escalations no one answered in time into instance-refusals (P4). */
25275
+ _expireEscalations(instance, tick) {
25276
+ const active = this._activeEscalations.get(instance.config.id);
25277
+ if (!active || active.size === 0) return;
25278
+ for (const [intentId, esc] of active) {
25279
+ if (tick < esc.expiresAt) continue;
25280
+ active.delete(intentId);
25281
+ this._clearEscalated(instance, intentId);
25282
+ this._queueRefusal(instance, esc.intentId, esc.schema, "ESCALATION_EXPIRED", "instance");
25283
+ logger.info(`[policy] escalation EXPIRED \u2192 refusing "${esc.schema}" intent "${intentId}"`);
25284
+ }
25285
+ }
25286
+ /** Push a refusal onto the queue drained by _applyRefusals this same tick. */
25287
+ _queueRefusal(instance, intentId, schema, reasonCode, finality) {
25288
+ const queue = this._pendingRefusals.get(instance.config.id) ?? [];
25289
+ queue.push({ intentId, schema, reasonCode, finality });
25290
+ this._pendingRefusals.set(instance.config.id, queue);
25291
+ }
25292
+ /** Mark the awaiting intent held: the executor stops timing it out (P4). */
25293
+ _markEscalated(instance, intentId, expiresAt) {
25294
+ const intent = instance.simulation.stateManager.snapshot().entities.get(intentId);
25295
+ if (!intent || intent.type !== "agency.intent") return;
25296
+ instance.simulation.stateManager.setEntity({
25297
+ id: intent.id,
25298
+ type: intent.type,
25299
+ metadata: { ...intent.metadata ?? {}, escalated: true, escalationExpiresAt: expiresAt }
25300
+ });
25301
+ }
25302
+ /** Release the hold so the executor resumes normal timeout for this intent. */
25303
+ _clearEscalated(instance, intentId) {
25304
+ const intent = instance.simulation.stateManager.snapshot().entities.get(intentId);
25305
+ if (!intent || intent.type !== "agency.intent") return;
25306
+ const meta = { ...intent.metadata ?? {} };
25307
+ delete meta["escalated"];
25308
+ delete meta["escalationExpiresAt"];
25309
+ instance.simulation.stateManager.setEntity({ id: intent.id, type: intent.type, metadata: meta });
25310
+ }
25311
+ /** Voice the escalation as a first-person broadcast ask — once, at raise time. */
25312
+ _voiceEscalation(instance, esc) {
25313
+ try {
25314
+ instance.cognition.outboxWriter.enqueue({
25315
+ targetEntityId: "*",
25316
+ content: escalationAsk(esc.schema, esc.reasonCode),
25317
+ effectorName: "broadcast"
25318
+ });
25319
+ } catch (err) {
25320
+ logger.warn(`[policy] escalation voice failed for "${esc.schema}": ${errMsg2(err)}`);
25321
+ }
25322
+ }
25323
+ /** Queue an approved invocation for the delivery layer. */
25324
+ _buffer(instance, payload) {
24944
25325
  const intentId = payload.intentId ?? "";
24945
25326
  instance.pendingEffectorInvocations.push({
24946
25327
  id: intentId,
@@ -25015,6 +25396,38 @@ var effectorController = class {
25015
25396
  function num5(v, fallback) {
25016
25397
  return typeof v === "number" && Number.isFinite(v) ? v : fallback;
25017
25398
  }
25399
+ function escalationAsk(schema, reasonCode) {
25400
+ const meaning = ESCALATION_MEANINGS[reasonCode] ?? "I need your approval before I can do this";
25401
+ return `I want to ${schema}, but ${meaning}. May I go ahead?`;
25402
+ }
25403
+ var ESCALATION_MEANINGS = {
25404
+ APPROVAL_REQUIRED: "I need your approval before I can on my own",
25405
+ WRITE_REQUIRES_APPROVAL: "it writes to the world and I shouldn't on my own",
25406
+ PAYMENT_REQUIRES_APPROVAL: "it moves money and I must not do that unattended",
25407
+ DEPLOY_REQUIRES_APPROVAL: "it ships something and needs a human to sign off"
25408
+ };
25409
+ function errMsg2(err) {
25410
+ return err instanceof Error ? err.message : String(err);
25411
+ }
25412
+ function recordToVerdict(record) {
25413
+ return {
25414
+ decision: record.decision,
25415
+ ...record.reasonCode ? { reasonCode: record.reasonCode } : {},
25416
+ ...record.finality ? { finality: record.finality } : {},
25417
+ ...record.counterfactual ? { counterfactual: record.counterfactual } : {}
25418
+ };
25419
+ }
25420
+ function toPolicyInvocation(instance, payload) {
25421
+ return {
25422
+ willId: instance.config.id,
25423
+ intentId: payload.intentId ?? "",
25424
+ schema: payload.schema ?? "",
25425
+ parameters: payload.parameters ?? {},
25426
+ ...typeof payload.targetEntityId === "string" ? { targetEntityId: payload.targetEntityId } : {},
25427
+ ...typeof payload.description === "string" ? { description: payload.description } : {},
25428
+ tick: payload.tick ?? 0
25429
+ };
25430
+ }
25018
25431
 
25019
25432
  // src/stem/tracts/sensory.controller.ts
25020
25433
  var SensoryController = class {
@@ -25817,6 +26230,15 @@ var WillStem = class {
25817
26230
  confirmEffectorExecution(id, invocationId, result) {
25818
26231
  this._effector.confirmExecution(this._get(id), invocationId, result);
25819
26232
  }
26233
+ /**
26234
+ * Resolve a policy escalation the Will raised (POLICY_REAFFERENCE P4).
26235
+ * `approved` dispatches the held invocation to the world; otherwise it is
26236
+ * refused. Applied at the next tick boundary. `invocationId` is the awaiting
26237
+ * `agency.intent` id the escalation ask referenced.
26238
+ */
26239
+ resolveEscalation(id, invocationId, approved) {
26240
+ this._effector.resolveEscalation(this._get(id), invocationId, approved);
26241
+ }
25820
26242
  // ── Messaging / outbox (11.1) ────────────────────────────────────────────
25821
26243
  // Delegates to OutboxController (R5-c). `_get(id)` validates the Will exists
25822
26244
  // and supplies the WillInstance; the outbox ops touch only instance fields.
@@ -25947,6 +26369,7 @@ var WillStem = class {
25947
26369
  outbox: this._outbox,
25948
26370
  sensory: this._sensory
25949
26371
  });
26372
+ this._effector.applyPolicyOutcomes(instance);
25950
26373
  await instance.simulation.step(1);
25951
26374
  instance.tickCount++;
25952
26375
  instance.lastTickAt = /* @__PURE__ */ new Date();