@mindot/will 0.5.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.
- package/dist/channels/discord.d.ts +1 -1
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +998 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +998 -59
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/mcp/effectors.js.map +1 -1
- package/dist/{will-BDq-TMQr.d.ts → will-DAW0l-lY.d.ts} +90 -10
- package/package.json +1 -1
- package/src/channels/discord.ts +11 -11
- package/src/channels/roster.ts +1 -1
- package/src/channels/types.ts +1 -1
- package/src/channels/whatsapp.ts +25 -25
- package/src/cli.ts +20 -20
- package/src/cognition/acp.ts +28 -0
- package/src/cognition/agency/consequence.ts +174 -0
- package/src/cognition/agency/engines/action.selector.ts +333 -48
- package/src/cognition/agency/engines/affordance.synthesizer.ts +35 -16
- package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
- package/src/cognition/agency/engines/motor.schema.executor.ts +90 -14
- package/src/cognition/agency/engines/reafference.engine.ts +152 -23
- package/src/cognition/agency/execution.primitives.ts +13 -13
- package/src/cognition/agency/proactive.communicator.ts +1 -1
- package/src/cognition/agency/reconcile.learning.ts +10 -1
- package/src/cognition/agency/revocation.ts +79 -0
- package/src/cognition/agency/schemas/repertoire.ts +109 -9
- package/src/cognition/agency/selection.scoring.ts +32 -10
- package/src/cognition/agency/types.ts +9 -0
- package/src/cognition/bus.ts +2 -2
- package/src/cognition/completion.inbox.ts +1 -1
- package/src/cognition/config.mirror.entities.ts +5 -5
- package/src/cognition/event.log.ts +2 -2
- package/src/cognition/event.schemas.ts +67 -67
- package/src/cognition/faculties/aesthetic.evaluator.ts +6 -6
- package/src/cognition/faculties/affective.blender.ts +36 -4
- package/src/cognition/faculties/attachment.evaluator.ts +6 -6
- package/src/cognition/faculties/attention.allocator.ts +63 -8
- package/src/cognition/faculties/autobiographical.narrator.ts +3 -3
- package/src/cognition/faculties/bias.detector.ts +7 -7
- package/src/cognition/faculties/circadian.oscillator.ts +3 -3
- package/src/cognition/faculties/confidence.calibrator.ts +7 -7
- package/src/cognition/faculties/dream.simulator.ts +5 -5
- package/src/cognition/faculties/empathy.simulator.ts +7 -7
- package/src/cognition/faculties/energy.regulator.ts +4 -4
- package/src/cognition/faculties/episodic.consolidator.ts +6 -6
- package/src/cognition/faculties/executive.engine/commands.ts +6 -6
- package/src/cognition/faculties/executive.engine/context.ts +42 -42
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +2 -2
- package/src/cognition/faculties/executive.engine/engine.ts +25 -25
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +12 -12
- package/src/cognition/faculties/executive.engine/facet.ts +18 -18
- package/src/cognition/faculties/executive.engine/gating.ts +2 -2
- package/src/cognition/faculties/executive.engine/messages.ts +3 -3
- package/src/cognition/faculties/executive.engine/parser.ts +11 -11
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +55 -55
- package/src/cognition/faculties/exteroception.ts +99 -5
- package/src/cognition/faculties/forgetting.curve.ts +3 -3
- package/src/cognition/faculties/frustration.evaluator.ts +6 -6
- package/src/cognition/faculties/goal.manager.ts +15 -15
- package/src/cognition/faculties/inhibition.controller.ts +3 -3
- package/src/cognition/faculties/interoception.ts +3 -3
- package/src/cognition/faculties/introspection.engine.ts +3 -3
- package/src/cognition/faculties/known.entity.tracker.ts +15 -15
- package/src/cognition/faculties/loss.evaluator.ts +2 -2
- package/src/cognition/faculties/moral.evaluator.ts +4 -4
- package/src/cognition/faculties/novelty.detector.ts +4 -4
- package/src/cognition/faculties/persona.consolidator.ts +12 -12
- package/src/cognition/faculties/planning.engine/engine.ts +47 -47
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +5 -5
- package/src/cognition/faculties/planning.engine/plan.store.ts +1 -1
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +20 -20
- package/src/cognition/faculties/reputation.tracker.ts +6 -6
- package/src/cognition/faculties/reward.evaluator.ts +4 -4
- package/src/cognition/faculties/self.model.updater.ts +8 -8
- package/src/cognition/faculties/semantic.engine/integrator.ts +15 -15
- package/src/cognition/faculties/sleep.pressure.regulator.ts +4 -4
- package/src/cognition/faculties/social.perception.ts +3 -3
- package/src/cognition/faculties/spaced.repetition.ts +2 -2
- package/src/cognition/faculties/stress.regulator.ts +27 -4
- package/src/cognition/faculties/task.switcher.ts +11 -6
- package/src/cognition/faculties/theory.of.mind.ts +6 -6
- package/src/cognition/faculties/threat.evaluator.ts +3 -3
- package/src/cognition/faculties/working.memory.ts +8 -8
- package/src/cognition/generative.model.ts +2 -2
- package/src/cognition/memory/vector.adapter.ts +5 -5
- package/src/cognition/memory/vector.content.ts +4 -4
- package/src/cognition/memory/vector.embedder.ts +9 -9
- package/src/cognition/persona.prior.ts +1 -1
- package/src/cognition/schema.registry.ts +2 -2
- package/src/cognition/senses/audition.engine/engine.ts +2 -2
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/utilities/token.tracker.ts +5 -5
- package/src/core/abstracts.ts +1 -1
- package/src/core/completion.recorder.ts +2 -2
- package/src/core/serialization.ts +3 -3
- package/src/core/snapshot.manager.ts +1 -1
- package/src/core/state.manager.ts +1 -1
- package/src/core/utils.ts +1 -1
- package/src/host/boot.ts +30 -30
- package/src/host/utterances.ts +1 -1
- package/src/llm/index.ts +9 -9
- package/src/llm/summarizer.ts +4 -4
- package/src/llm/wire.contracts.ts +1 -1
- package/src/mcp/effectors.ts +10 -10
- package/src/mcp/server.ts +11 -11
- package/src/pma/eval.ts +1 -1
- package/src/pma/index.ts +21 -21
- package/src/runners/outreach.runner.ts +3 -3
- package/src/runners/social.runner.ts +1 -1
- package/src/runners/thin-shim.runner.ts +1 -1
- package/src/sdk/will.ts +5 -5
- package/src/serve/server.ts +12 -12
- package/src/stem/assembly.audit.ts +3 -3
- package/src/stem/guards/identity.coherence.ts +10 -10
- package/src/stem/guards/identity.guard.ts +22 -22
- package/src/stem/index.ts +26 -11
- package/src/stem/mind.ts +12 -12
- package/src/stem/policy/arbiter.ts +136 -0
- package/src/stem/policy/rule.table.ts +172 -0
- package/src/stem/policy/verdict.recorder.ts +0 -0
- package/src/stem/tracts/biography.writer.ts +3 -3
- package/src/stem/tracts/effector.controller.ts +383 -4
- package/src/stem/tracts/health.reporter.ts +1 -1
- package/src/stem/tracts/outbox.controller.ts +4 -0
- package/src/stem/tracts/outbox.writer.ts +4 -4
- package/src/stem/tracts/pma.controller.ts +1 -1
- package/src/stem/tracts/replay.controller.ts +10 -10
- package/src/stem/tracts/sensory.controller.ts +2 -2
- package/src/stem/tracts/session.logger.ts +4 -4
- package/src/stem/tracts/transport/socketio.transport.ts +7 -7
- package/src/stem/tracts/transport/stream.transport.ts +2 -2
- package/src/stem/tracts/transport.controller.ts +15 -15
|
@@ -28,10 +28,39 @@ import type { CognitiveBus, CognitiveEvent } from '#cognition/bus'
|
|
|
28
28
|
import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
29
29
|
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
30
30
|
import type { SchemaRepertoire } from '#agency/schemas/repertoire'
|
|
31
|
-
import { schemaEntityId } from '#agency/schemas/repertoire'
|
|
31
|
+
import { schemaEntityId, availabilityEntityId } from '#agency/schemas/repertoire'
|
|
32
|
+
import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
|
|
32
33
|
|
|
33
34
|
const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* EXAFFERENCE P5 — soft outcome quality for a purely *sensory* confirmation: the
|
|
38
|
+
* world echoed our action back (a reafferent percept), but no host ack graded it.
|
|
39
|
+
* A modest positive — "it manifested" — below a host ack's 0.8, enough to free the
|
|
40
|
+
* awaiting intent and accrue competence instead of letting it time out as a failure.
|
|
41
|
+
*/
|
|
42
|
+
const SENSORY_SOFT_QUALITY = 0.6
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Registry #5 — the affect→percept valence seam, consumed. The echo now carries
|
|
46
|
+
* how the mind FEELS about what came back (Exteroception stamps it), so a
|
|
47
|
+
* sensory confirmation is graded rather than flat: quality = the soft base
|
|
48
|
+
* shifted by that valence within a bounded band.
|
|
49
|
+
*
|
|
50
|
+
* Two spans, because the two valence sources are not equal evidence:
|
|
51
|
+
* • `'entity'` — the KnownEntityTracker's felt valence toward this very thing.
|
|
52
|
+
* Real appraisal of the outcome ⇒ the wider span.
|
|
53
|
+
* • `'ambient'` — the mind's mood at perception time. Weak context, NOT a
|
|
54
|
+
* judgement of this action ⇒ a narrow span, so a bad mood cannot teach the
|
|
55
|
+
* Will that a working skill failed.
|
|
56
|
+
*
|
|
57
|
+
* Neutral (or absent) valence reproduces the pre-seam 0.6 exactly.
|
|
58
|
+
*/
|
|
59
|
+
const SENSORY_VALENCE_SPAN_ENTITY = 0.25
|
|
60
|
+
const SENSORY_VALENCE_SPAN_AMBIENT = 0.10
|
|
61
|
+
/** Below this the world's reflection reads as a failed outcome, not a success. */
|
|
62
|
+
const SENSORY_SUCCESS_FLOOR = 0.5
|
|
63
|
+
|
|
35
64
|
export class ReafferenceEngine implements CognitiveEngine {
|
|
36
65
|
readonly name = 'reafference'
|
|
37
66
|
|
|
@@ -56,11 +85,11 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
56
85
|
/** Creation seam: register a composite proposed by the executive/deliberation facet. */
|
|
57
86
|
subscribes(): string[] { return [ 'agency.composite.proposed' ] }
|
|
58
87
|
onCognitiveEvent( e: CognitiveEvent ): void {
|
|
59
|
-
if( e.type !== 'agency.composite.proposed'
|
|
88
|
+
if( e.type !== 'agency.composite.proposed') return
|
|
60
89
|
const p = e.payload as { id?: string; name?: string; composedOf?: unknown; tags?: unknown; cost?: number }
|
|
61
90
|
const id = p.id ?? p.name
|
|
62
91
|
const steps = Array.isArray( p.composedOf )
|
|
63
|
-
? ( p.composedOf as unknown[] ).filter( ( s ): s is string => typeof s === 'string'
|
|
92
|
+
? ( p.composedOf as unknown[] ).filter( ( s ): s is string => typeof s === 'string')
|
|
64
93
|
: []
|
|
65
94
|
if( !id || steps.length < 2 ) return // a composite needs a name + ≥2 sub-schemas
|
|
66
95
|
|
|
@@ -68,9 +97,9 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
68
97
|
id, kind: 'composite', source: 'repertoire', binds: 'none',
|
|
69
98
|
cost: typeof p.cost === 'number' ? p.cost : 0.1,
|
|
70
99
|
composedOf: steps,
|
|
71
|
-
tags: Array.isArray( p.tags ) ? ( p.tags as unknown[] ).filter( ( t ): t is string => typeof t === 'string'
|
|
100
|
+
tags: Array.isArray( p.tags ) ? ( p.tags as unknown[] ).filter( ( t ): t is string => typeof t === 'string') : [ 'composite' ],
|
|
72
101
|
})
|
|
73
|
-
logger.info(
|
|
102
|
+
logger.info(`[reafference] registered proposed composite "${ id }" (${ steps.join(' → ') })`)
|
|
74
103
|
}
|
|
75
104
|
snapshot(): Record<string, unknown> {
|
|
76
105
|
return { skills: this._repertoire.skills().size }
|
|
@@ -86,14 +115,102 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
86
115
|
const del: string[] = []
|
|
87
116
|
const metrics: Array<[ string, number ]> = []
|
|
88
117
|
|
|
89
|
-
// ──
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
// ── 0. Gather outcomes: real ones from state + synthesized sensory ones ──
|
|
119
|
+
// A `reafferent` percept (P2) carrying a `sourceIntentId` is the world echoing
|
|
120
|
+
// our own action back. If that intent is still `awaiting` and no outcome graded
|
|
121
|
+
// it this tick (a host ack / sync / timeout — those WIN), synthesize a soft
|
|
122
|
+
// outcome so the skill learns and the intent is freed through the senses rather
|
|
123
|
+
// than timing out as a failure (EXAFFERENCE P5). Ack-less awaiting intents only.
|
|
124
|
+
const outcomes: Array<{ id: string; meta: Record<string, unknown>; fromState: boolean }> = []
|
|
125
|
+
const gradedIntentIds = new Set<string>()
|
|
92
126
|
for( const [ id, e ] of state.entities ){
|
|
93
|
-
if( e.type !== 'agency.outcome'
|
|
127
|
+
if( e.type !== 'agency.outcome') continue
|
|
128
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
129
|
+
outcomes.push({ id, meta: m, fromState: true })
|
|
130
|
+
const iid = str( m['intentId'] ); if( iid ) gradedIntentIds.add( iid )
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const awaiting = new Map<string, { schema: string; predictedReward: number; predictedValence: number }>()
|
|
134
|
+
for( const [ id, e ] of state.entities ){
|
|
135
|
+
if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
|
|
136
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
137
|
+
// Timeout boundary: at tick - dispatchedAt ≥ AWAIT_TIMEOUT the executor
|
|
138
|
+
// (which ran earlier this tick, off the same frozen snapshot) is writing
|
|
139
|
+
// its timeout-failure outcome right now — stand down so one enaction is
|
|
140
|
+
// never scored twice (soft success here + failure next tick).
|
|
141
|
+
if( tick - num( m['dispatchedAt'], tick ) >= AWAIT_TIMEOUT ) continue
|
|
142
|
+
awaiting.set( id, {
|
|
143
|
+
schema: str( m['schema'] ) ?? '',
|
|
144
|
+
predictedReward: num( m['predictedReward'], 0.5 ),
|
|
145
|
+
predictedValence: num( m['predictedValence'], 0 ),
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let sensory = 0
|
|
150
|
+
const sensedIntentIds = new Set<string>()
|
|
151
|
+
for( const [ , e ] of state.entities ){
|
|
152
|
+
if( e.type !== 'percept') continue
|
|
94
153
|
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
154
|
+
if( str( m['provenance'] ) !== 'reafferent') continue
|
|
155
|
+
const iid = str( m['sourceIntentId'] )
|
|
156
|
+
if( !iid || gradedIntentIds.has( iid ) || sensedIntentIds.has( iid ) ) continue
|
|
157
|
+
const aw = awaiting.get( iid )
|
|
158
|
+
if( !aw || !aw.schema ) continue // only ack-less awaiting intents
|
|
159
|
+
sensedIntentIds.add( iid )
|
|
160
|
+
sensory++
|
|
161
|
+
|
|
162
|
+
// Registry #5: grade the confirmation by how the echo FELT, within a band
|
|
163
|
+
// whose width reflects how much that valence really says about THIS action.
|
|
164
|
+
const feltRaw = m['valence']
|
|
165
|
+
const felt = typeof feltRaw === 'number' && Number.isFinite( feltRaw ) ? feltRaw : undefined
|
|
166
|
+
const span = str( m['valenceSource'] ) === 'entity'
|
|
167
|
+
? SENSORY_VALENCE_SPAN_ENTITY
|
|
168
|
+
: SENSORY_VALENCE_SPAN_AMBIENT
|
|
169
|
+
const quality = felt === undefined
|
|
170
|
+
? SENSORY_SOFT_QUALITY
|
|
171
|
+
: clamp01( SENSORY_SOFT_QUALITY + Math.max( -1, Math.min( 1, felt ) ) * span )
|
|
172
|
+
|
|
173
|
+
outcomes.push({ id: `agency-outcome-${ tick }-${ iid }-sensory`, fromState: false, meta: {
|
|
174
|
+
schema: aw.schema,
|
|
175
|
+
intentId: iid,
|
|
176
|
+
success: quality >= SENSORY_SUCCESS_FLOOR,
|
|
177
|
+
outcomeQuality: quality,
|
|
178
|
+
valence: felt ?? aw.predictedValence,
|
|
179
|
+
predictedReward: aw.predictedReward,
|
|
180
|
+
predictedValence: aw.predictedValence,
|
|
181
|
+
surprise: clamp01( Math.abs( aw.predictedReward - quality ) ),
|
|
182
|
+
mode: 'external',
|
|
183
|
+
reconciled: true,
|
|
184
|
+
sensory: true,
|
|
185
|
+
...( felt !== undefined ? { sensoryValence: felt, valenceSource: str( m['valenceSource'] ) } : {} ),
|
|
186
|
+
tick,
|
|
187
|
+
} })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── 1. Fold each outcome into its skill ──────────────────────
|
|
191
|
+
let updates = 0
|
|
192
|
+
let discovered = 0
|
|
193
|
+
let refused = 0
|
|
194
|
+
for( const { id, meta: m, fromState } of outcomes ){
|
|
95
195
|
const schema = str( m['schema'] )
|
|
96
|
-
if( !schema ){ del.push( id ); continue }
|
|
196
|
+
if( !schema ){ if( fromState ) del.push( id ); continue }
|
|
197
|
+
|
|
198
|
+
// POLICY_REAFFERENCE P2 — a refusal is NOT a failure. Route it to the
|
|
199
|
+
// availability layer and stop: it must never touch LearnedSkill (value,
|
|
200
|
+
// habit, param priors), or the Will learns it is unskilled at something it
|
|
201
|
+
// is merely forbidden to do. The awaiting intent is still freed, and a
|
|
202
|
+
// refused plan step is signalled unsuccessful so the plan doesn't hang.
|
|
203
|
+
if( m['refused'] === true ){
|
|
204
|
+
const finality = str( m['finality'] ) === 'class' ? 'class' : 'instance'
|
|
205
|
+
this._repertoire.recordRefusal( schema, finality, tick )
|
|
206
|
+
if( fromState ) del.push( id )
|
|
207
|
+
const refusedIntent = str( m['intentId'] )
|
|
208
|
+
if( refusedIntent ) del.push( refusedIntent )
|
|
209
|
+
const refusedPlan = str( m['planId'] )
|
|
210
|
+
if( refusedPlan ) this._emitPlanOutcome( refusedPlan, str( m['stepId'] ), schema, false, 0, 0, tick )
|
|
211
|
+
refused++
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
97
214
|
|
|
98
215
|
const { skill, proceduralized } = this._repertoire.recordOutcome({
|
|
99
216
|
schema,
|
|
@@ -105,10 +222,11 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
105
222
|
})
|
|
106
223
|
|
|
107
224
|
set.push( skillEntity( skill ) )
|
|
108
|
-
del.push( id )
|
|
109
|
-
// Host-ack reconciliation:
|
|
110
|
-
// id — free it so the serial Will can act again. The
|
|
111
|
-
// outcomes already deleted their intent, so this is a
|
|
225
|
+
if( fromState ) del.push( id ) // real outcome consumed (synthetic ones were never in state)
|
|
226
|
+
// Host-ack reconciliation OR sensory confirmation: the outcome carries the
|
|
227
|
+
// awaiting intent's id — free it so the serial Will can act again. The
|
|
228
|
+
// executor's own sync outcomes already deleted their intent, so this is a
|
|
229
|
+
// harmless no-op there.
|
|
112
230
|
const intentId = str( m['intentId'] )
|
|
113
231
|
if( intentId ) del.push( intentId )
|
|
114
232
|
updates++
|
|
@@ -131,23 +249,27 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
131
249
|
|
|
132
250
|
if( proceduralized ){
|
|
133
251
|
this._emitProceduralized( skill, tick )
|
|
134
|
-
logger.info(
|
|
252
|
+
logger.info(`[reafference] "${ schema }" proceduralized (habit ${ skill.habitStrength.toFixed( 2 ) })`)
|
|
135
253
|
}
|
|
136
254
|
}
|
|
137
255
|
|
|
138
|
-
// ── 2. Forgetting curve over the competence layer
|
|
256
|
+
// ── 2. Forgetting curve over the competence layer + availability recovery ──
|
|
139
257
|
const dropped = this._repertoire.decay( tick )
|
|
140
|
-
for( const id of dropped ){
|
|
141
|
-
del.push(
|
|
258
|
+
for( const id of dropped.skills ){
|
|
259
|
+
del.push(`agency-skill-${ id }`)
|
|
142
260
|
del.push( schemaEntityId( id ) ) // composite mirror (harmless no-op for primitives)
|
|
143
261
|
}
|
|
262
|
+
for( const id of dropped.availability )
|
|
263
|
+
del.push( availabilityEntityId( id ) ) // fully-recovered ⇒ remove the mirror
|
|
144
264
|
|
|
145
|
-
// ── 3. Mirror learned composite templates
|
|
265
|
+
// ── 3. Mirror learned composite templates + availability ─────
|
|
146
266
|
// Skills become `agency.skill` (above); the invented composite *definitions*
|
|
147
267
|
// must travel too, or a snapshot/restore brings back a skill whose schema is
|
|
148
268
|
// gone and the executor can't expand it. Idempotent re-write each tick, like
|
|
149
269
|
// GoalManager._persistGoals. Empty until a composite is actually learned.
|
|
150
|
-
for( const e of this._repertoire.compositeEntities() )
|
|
270
|
+
for( const e of this._repertoire.compositeEntities() ) set.push( e )
|
|
271
|
+
// Availability entries (P2) mirror the same way — empty until a refusal lands.
|
|
272
|
+
for( const e of this._repertoire.availabilityEntities() ) set.push( e )
|
|
151
273
|
|
|
152
274
|
// ── 4. Telemetry ─────────────────────────────────────────────
|
|
153
275
|
const skills = this._repertoire.skills()
|
|
@@ -157,7 +279,11 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
157
279
|
[ 'agency.discovered.count', discovered ],
|
|
158
280
|
[ 'agency.skill.count', skills.size ],
|
|
159
281
|
[ 'agency.habitual.count', habitual ],
|
|
282
|
+
[ 'agency.sensory.confirmed', sensory ],
|
|
160
283
|
)
|
|
284
|
+
// Only emit the refusal metric when it fired — a never-refused Will writes
|
|
285
|
+
// nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
|
|
286
|
+
if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
|
|
161
287
|
|
|
162
288
|
return { commands: { set, delete: del, metrics } }
|
|
163
289
|
}
|
|
@@ -171,7 +297,7 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
171
297
|
payload: { schema: skill.schema, habitStrength: skill.habitStrength, tick },
|
|
172
298
|
})
|
|
173
299
|
}
|
|
174
|
-
catch( err ){ logger.warn(
|
|
300
|
+
catch( err ){ logger.warn(`[reafference] publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
175
301
|
}
|
|
176
302
|
|
|
177
303
|
/**
|
|
@@ -197,7 +323,7 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
197
323
|
},
|
|
198
324
|
})
|
|
199
325
|
}
|
|
200
|
-
catch( err ){ logger.warn(
|
|
326
|
+
catch( err ){ logger.warn(`[reafference] plan outcome publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
201
327
|
}
|
|
202
328
|
|
|
203
329
|
private _emitDiscovered( schema: string, tick: Tick ): void {
|
|
@@ -208,7 +334,7 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
208
334
|
salience: 0.5, payload: { schema, tick },
|
|
209
335
|
})
|
|
210
336
|
}
|
|
211
|
-
catch( err ){ logger.warn(
|
|
337
|
+
catch( err ){ logger.warn(`[reafference] discovered publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
212
338
|
}
|
|
213
339
|
}
|
|
214
340
|
|
|
@@ -239,3 +365,6 @@ function str( v: unknown ): string | undefined {
|
|
|
239
365
|
function num( v: unknown, fallback: number ): number {
|
|
240
366
|
return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
|
|
241
367
|
}
|
|
368
|
+
function clamp01( n: number ): number {
|
|
369
|
+
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
370
|
+
}
|
|
@@ -46,8 +46,8 @@ const COMM_SCHEMAS = new Set([ 'reach-out', 'talk', 'text', 'broadcast', 'gestur
|
|
|
46
46
|
|
|
47
47
|
/** Classify how a primitive schema executes. */
|
|
48
48
|
export function modeOf( schema: MotorSchema ): EnactionMode {
|
|
49
|
-
if( schema.tags?.includes(
|
|
50
|
-
if( schema.tags?.includes(
|
|
49
|
+
if( schema.tags?.includes('external') ) return 'external'
|
|
50
|
+
if( schema.tags?.includes('communication') || COMM_SCHEMAS.has( schema.id ) ) return 'communicate'
|
|
51
51
|
return 'sync'
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -55,7 +55,7 @@ export function modeOf( schema: MotorSchema ): EnactionMode {
|
|
|
55
55
|
export function enact( ctx: EnactionContext ): Enaction {
|
|
56
56
|
const mode = modeOf( ctx.schema )
|
|
57
57
|
|
|
58
|
-
if( mode === 'communicate'
|
|
58
|
+
if( mode === 'communicate'){
|
|
59
59
|
const name = str( ctx.parameters['targetEntityName'] ) ?? ctx.targetEntityId ?? 'them'
|
|
60
60
|
return {
|
|
61
61
|
mode, success: true, outcomeQuality: 0.7, valence: 0.1,
|
|
@@ -63,7 +63,7 @@ export function enact( ctx: EnactionContext ): Enaction {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
if( mode === 'external'
|
|
66
|
+
if( mode === 'external')
|
|
67
67
|
return {
|
|
68
68
|
mode, success: true, outcomeQuality: 0.5, valence: 0,
|
|
69
69
|
description: `"${ ctx.schema.id }" dispatched to the host; awaiting the world's reply.`,
|
|
@@ -82,25 +82,25 @@ function syncStance( ctx: EnactionContext ): Enaction {
|
|
|
82
82
|
switch( schema.id ){
|
|
83
83
|
case 'rest':
|
|
84
84
|
// More restorative the more depleted the Will was.
|
|
85
|
-
return sync( 0.5 + ( 1 - e01 ) * 0.4, 0.15, 'I let myself recover; the pressure eases a little.'
|
|
85
|
+
return sync( 0.5 + ( 1 - e01 ) * 0.4, 0.15, 'I let myself recover; the pressure eases a little.')
|
|
86
86
|
case 'withdraw':
|
|
87
|
-
return sync( 0.5 + s01 * 0.3, 0.05 + s01 * 0.1, 'I pull back from the press of things; the world quietens.'
|
|
87
|
+
return sync( 0.5 + s01 * 0.3, 0.05 + s01 * 0.1, 'I pull back from the press of things; the world quietens.')
|
|
88
88
|
case 'reflect':
|
|
89
|
-
return sync( 0.6, 0.05, 'I turn inward; patterns from recent events settle into place.'
|
|
89
|
+
return sync( 0.6, 0.05, 'I turn inward; patterns from recent events settle into place.')
|
|
90
90
|
case 'attend':
|
|
91
|
-
return sync( 0.6, 0.0, 'I concentrate, mobilizing more of my attention.'
|
|
91
|
+
return sync( 0.6, 0.0, 'I concentrate, mobilizing more of my attention.')
|
|
92
92
|
case 'orient':
|
|
93
|
-
return sync( 0.5, 0.0, 'My awareness sweeps the situation, taking its measure.'
|
|
93
|
+
return sync( 0.5, 0.0, 'My awareness sweeps the situation, taking its measure.')
|
|
94
94
|
case 'wait':
|
|
95
|
-
return sync( 0.5, 0.0, 'I let time pass; regulatory processes continue their quiet work.'
|
|
95
|
+
return sync( 0.5, 0.0, 'I let time pass; regulatory processes continue their quiet work.')
|
|
96
96
|
case 'express':
|
|
97
|
-
return sync( 0.6, 0.1, 'My inner state becomes outwardly visible.'
|
|
97
|
+
return sync( 0.6, 0.1, 'My inner state becomes outwardly visible.')
|
|
98
98
|
case 'inspect': {
|
|
99
99
|
const focus = str( parameters['focus'] ) ?? 'it'
|
|
100
|
-
return sync( 0.65, 0.05, `I examine ${ focus } closely; more of its detail resolves.`
|
|
100
|
+
return sync( 0.65, 0.05, `I examine ${ focus } closely; more of its detail resolves.`)
|
|
101
101
|
}
|
|
102
102
|
default:
|
|
103
|
-
return sync( 0.5, 0.0, `I enact ${ schema.id }.`
|
|
103
|
+
return sync( 0.5, 0.0, `I enact ${ schema.id }.`)
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -24,6 +24,14 @@ export interface HostAckResult {
|
|
|
24
24
|
/** −1..1 felt valence of the outcome. */
|
|
25
25
|
valence?: number
|
|
26
26
|
description?: string
|
|
27
|
+
/**
|
|
28
|
+
* POLICY_REAFFERENCE P2 — this ack is a policy REFUSAL, not a world failure.
|
|
29
|
+
* The ReafferenceEngine routes it to the availability layer (NOT competence):
|
|
30
|
+
* a refusal must never teach the Will it is unskilled at something it is merely
|
|
31
|
+
* forbidden to do. `finality` decides how hard availability is cut.
|
|
32
|
+
*/
|
|
33
|
+
refused?: boolean
|
|
34
|
+
finality?: 'class' | 'instance'
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -67,10 +75,11 @@ export function reconcileInvocation(
|
|
|
67
75
|
predictedReward: predicted.reward,
|
|
68
76
|
predictedValence: predicted.valence,
|
|
69
77
|
surprise,
|
|
70
|
-
description: result.description ?? ( result.success ? 'The world confirmed the action.' : 'The world rejected the action.'
|
|
78
|
+
description: result.description ?? ( result.success ? 'The world confirmed the action.' : 'The world rejected the action.'),
|
|
71
79
|
mode: 'external',
|
|
72
80
|
tick,
|
|
73
81
|
reconciled: true,
|
|
82
|
+
...( result.refused ? { refused: true, finality: result.finality ?? 'instance' } : {} ),
|
|
74
83
|
...( provenance.planId ? { planId: provenance.planId } : {} ),
|
|
75
84
|
...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
|
|
76
85
|
},
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/agency/revocation.ts — commitment-revocation tombstones
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// EXAFFERENCE P4. When the world ruptures hard enough (rupture ≥
|
|
6
|
+
// RUPTURE_REVOKE_GATE), the ActionSelector doesn't just soften its switch cost
|
|
7
|
+
// (P3) — it *lets go* of a commitment it was still weighing: a `deliberating`
|
|
8
|
+
// intent. But the selector cannot delete that intent directly. Registration
|
|
9
|
+
// order is Selector → Deliberation → Executor, and each engine reads the frozen
|
|
10
|
+
// tick-start snapshot, so a same-tick selector-delete + deliberation-set on the
|
|
11
|
+
// same intent applies set-after-delete → the intent is RESURRECTED as `selected`
|
|
12
|
+
// and enacted despite revocation (the P0 audit's resurrection race).
|
|
13
|
+
//
|
|
14
|
+
// The tombstone breaks that race by construction: the selector writes a SEPARATE
|
|
15
|
+
// `agency.revocation` entity keyed by the intent id (no same-entity write). One
|
|
16
|
+
// tick later both honorers see it from frozen state:
|
|
17
|
+
// • the Deliberation engine skips + deletes tombstoned `deliberating` intents;
|
|
18
|
+
// • the Executor refuses to enact a tombstoned `selected` intent (the half-race
|
|
19
|
+
// where deliberation committed `selected` the same tick the tombstone landed).
|
|
20
|
+
// Revocation commits NO successor — the field re-forms and next tick selects.
|
|
21
|
+
// A short TTL reaps a tombstone whose intent already vanished for another reason.
|
|
22
|
+
// ─────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
import type { EntityInput, Tick } from '#core/types'
|
|
25
|
+
|
|
26
|
+
export const REVOCATION_TYPE = 'agency.revocation'
|
|
27
|
+
|
|
28
|
+
/** Rupture at/above which a still-deliberating commitment is revoked (rarer than
|
|
29
|
+
* P3's recruitment-grade softening — letting go is a bigger step than easing up). */
|
|
30
|
+
export const RUPTURE_REVOKE_GATE = 0.7
|
|
31
|
+
|
|
32
|
+
/** Safety reap: a tombstone whose intent disappeared otherwise dies after this. */
|
|
33
|
+
export const REVOCATION_TTL_TICKS = 5
|
|
34
|
+
|
|
35
|
+
export function revocationId( intentId: string ): string {
|
|
36
|
+
return `agency-revocation-${ intentId }`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Build the tombstone for a revoked intent (keyed by intent id — one per intent). */
|
|
40
|
+
export function revocationEntity(
|
|
41
|
+
intentId: string, schema: string, rupture: number, tick: Tick,
|
|
42
|
+
): EntityInput {
|
|
43
|
+
return {
|
|
44
|
+
id: revocationId( intentId ),
|
|
45
|
+
type: REVOCATION_TYPE,
|
|
46
|
+
metadata: { intentId, schema, rupture, tick, expiresAt: tick + REVOCATION_TTL_TICKS },
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Intent ids under a live (unexpired) revocation tombstone, from frozen state. */
|
|
51
|
+
export function revokedIntentIds(
|
|
52
|
+
entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
|
|
53
|
+
tick: Tick,
|
|
54
|
+
): Set<string> {
|
|
55
|
+
const out = new Set<string>()
|
|
56
|
+
for( const [ , e ] of entities ){
|
|
57
|
+
if( e.type !== REVOCATION_TYPE ) continue
|
|
58
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
59
|
+
const intentId = typeof m['intentId'] === 'string' ? m['intentId'] as string : undefined
|
|
60
|
+
const expiresAt = typeof m['expiresAt'] === 'number' ? m['expiresAt'] as number : 0
|
|
61
|
+
if( intentId && tick < expiresAt ) out.add( intentId )
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Ids of expired tombstones to sweep (TTL reap for intents that vanished otherwise). */
|
|
67
|
+
export function staleRevocationIds(
|
|
68
|
+
entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
|
|
69
|
+
tick: Tick,
|
|
70
|
+
): string[] {
|
|
71
|
+
const stale: string[] = []
|
|
72
|
+
for( const [ id, e ] of entities ){
|
|
73
|
+
if( e.type !== REVOCATION_TYPE ) continue
|
|
74
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
75
|
+
const expiresAt = typeof m['expiresAt'] === 'number' ? m['expiresAt'] as number : 0
|
|
76
|
+
if( tick >= expiresAt ) stale.push( id )
|
|
77
|
+
}
|
|
78
|
+
return stale
|
|
79
|
+
}
|
|
@@ -39,6 +39,23 @@ const IDLE_TICKS = 200 // ticks of disuse before forgetting starts
|
|
|
39
39
|
const DECAY_RATE = 0.02 // habit lost per decay application
|
|
40
40
|
const DROP_HABIT = 0.05 // below this (and learned) the skill is forgotten
|
|
41
41
|
|
|
42
|
+
// ── availability layer (POLICY_REAFFERENCE P2) ────────────────
|
|
43
|
+
// Availability is NOT competence. It answers "may I use this schema", learned
|
|
44
|
+
// from policy refusals, and is kept strictly apart from LearnedSkill so a
|
|
45
|
+
// refusal never teaches the Will it is *unskilled* at something it is merely
|
|
46
|
+
// *forbidden* to do. A `class` refusal ("never, under this policy") drives
|
|
47
|
+
// availability down hard; an `instance` refusal ("not with those parameters")
|
|
48
|
+
// dents it lightly — the Will should keep reaching for the ability, just not
|
|
49
|
+
// that way. Recovery is slow but real, so a policy change is re-discoverable:
|
|
50
|
+
// availability never floors at zero, and it climbs back toward 1 with disuse of
|
|
51
|
+
// the refusal. P2 keys availability by SCHEMA; per-(schema, params) envelope
|
|
52
|
+
// narrowing is a follow-up that belongs at selection time, not fielding time.
|
|
53
|
+
const AVAIL_DROP_CLASS = 0.50 // multiplicative cut on a class-final refusal
|
|
54
|
+
const AVAIL_DROP_INSTANCE = 0.12 // lighter cut on an instance-final refusal
|
|
55
|
+
const AVAIL_FLOOR = 0.05 // never zero — re-probe must always be possible
|
|
56
|
+
const AVAIL_RECOVERY = 0.02 // per-decay climb back toward 1
|
|
57
|
+
const AVAIL_RECOVERED = 0.999 // at/above this the entry is dropped (quiet path)
|
|
58
|
+
|
|
42
59
|
export interface OutcomeObservation {
|
|
43
60
|
schema: string
|
|
44
61
|
success: boolean
|
|
@@ -54,6 +71,9 @@ export class SchemaRepertoire {
|
|
|
54
71
|
private _skills = new Map<string, LearnedSkill>()
|
|
55
72
|
/** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
|
|
56
73
|
private _learned = new Set<string>()
|
|
74
|
+
/** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
|
|
75
|
+
* a refusal lands — a never-refused Will writes nothing here (byte-identical). */
|
|
76
|
+
private _availability = new Map<string, { value: number; lastRefusedTick: number }>()
|
|
57
77
|
|
|
58
78
|
constructor( seed: MotorSchema[] = INNATE_SCHEMAS ){
|
|
59
79
|
for( const s of seed ) this._templates.set( s.id, s )
|
|
@@ -85,6 +105,32 @@ export class SchemaRepertoire {
|
|
|
85
105
|
skills(): ReadonlyMap<string, LearnedSkill> { return this._skills }
|
|
86
106
|
getSkill( id: string ): LearnedSkill | undefined { return this._skills.get( id ) }
|
|
87
107
|
|
|
108
|
+
// ── availability (P2) ─────────────────────────────────────────
|
|
109
|
+
availability(): ReadonlyMap<string, { value: number; lastRefusedTick: number }> { return this._availability }
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
|
|
113
|
+
* (fully available — the common case). This is the ONLY value the
|
|
114
|
+
* AffordanceSynthesizer reads; it never touches competence.
|
|
115
|
+
*/
|
|
116
|
+
availabilityOf( schema: string ): number {
|
|
117
|
+
return this._availability.get( schema )?.value ?? 1
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
122
|
+
* `class` refusal cuts availability hard; an `instance` refusal dents it
|
|
123
|
+
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
124
|
+
* reach — zero, keeping re-probe alive.
|
|
125
|
+
*/
|
|
126
|
+
recordRefusal( schema: string, finality: 'class' | 'instance', tick: number ): number {
|
|
127
|
+
const prev = this._availability.get( schema )?.value ?? 1
|
|
128
|
+
const drop = finality === 'class' ? AVAIL_DROP_CLASS : AVAIL_DROP_INSTANCE
|
|
129
|
+
const value = Math.max( AVAIL_FLOOR, prev * ( 1 - drop ) )
|
|
130
|
+
this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
131
|
+
return value
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
/**
|
|
89
135
|
* Fold one outcome into the schema's learned skill. Returns the updated skill
|
|
90
136
|
* and whether it just crossed the proceduralization threshold this update.
|
|
@@ -118,12 +164,14 @@ export class SchemaRepertoire {
|
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
/**
|
|
121
|
-
* Forgetting curve over the competence layer
|
|
122
|
-
* lose habit; learned composites
|
|
123
|
-
* entirely (template + skill).
|
|
167
|
+
* Forgetting curve over the competence layer, plus availability recovery.
|
|
168
|
+
* Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
|
|
169
|
+
* are dropped entirely (template + skill). Availability entries climb back
|
|
170
|
+
* toward 1 and are dropped once fully recovered. Returns the ids that were
|
|
171
|
+
* removed from each layer so their mirrored state entities can be deleted.
|
|
124
172
|
*/
|
|
125
|
-
decay( tick: number ): string[] {
|
|
126
|
-
const
|
|
173
|
+
decay( tick: number ): { skills: string[]; availability: string[] } {
|
|
174
|
+
const skills: string[] = []
|
|
127
175
|
for( const [ id, skill ] of this._skills ){
|
|
128
176
|
if( tick - skill.lastEnactedTick <= IDLE_TICKS ) continue
|
|
129
177
|
|
|
@@ -132,12 +180,23 @@ export class SchemaRepertoire {
|
|
|
132
180
|
this._skills.delete( id )
|
|
133
181
|
this._templates.delete( id )
|
|
134
182
|
this._learned.delete( id )
|
|
135
|
-
|
|
183
|
+
skills.push( id )
|
|
136
184
|
continue
|
|
137
185
|
}
|
|
138
186
|
this._skills.set( id, { ...skill, habitStrength } )
|
|
139
187
|
}
|
|
140
|
-
|
|
188
|
+
|
|
189
|
+
// Availability recovery (P2): each entry climbs slowly back toward 1, so a
|
|
190
|
+
// policy change is re-discoverable. A fully-recovered entry is dropped, so a
|
|
191
|
+
// Will that was refused long ago returns to the byte-identical quiet path.
|
|
192
|
+
const availability: string[] = []
|
|
193
|
+
for( const [ id, avail ] of this._availability ){
|
|
194
|
+
const value = avail.value + AVAIL_RECOVERY * ( 1 - avail.value )
|
|
195
|
+
if( value >= AVAIL_RECOVERED ){ this._availability.delete( id ); availability.push( id ) }
|
|
196
|
+
else this._availability.set( id, { ...avail, value } )
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { skills, availability }
|
|
141
200
|
}
|
|
142
201
|
|
|
143
202
|
// ── PMA portability (Phase 6 reads these) ─────────────────────
|
|
@@ -167,7 +226,7 @@ export class SchemaRepertoire {
|
|
|
167
226
|
const out: EntityInput[] = []
|
|
168
227
|
for( const id of this._learned ){
|
|
169
228
|
const s = this._templates.get( id )
|
|
170
|
-
if( s && s.kind === 'composite'
|
|
229
|
+
if( s && s.kind === 'composite') out.push( schemaEntity( s ) )
|
|
171
230
|
}
|
|
172
231
|
return out
|
|
173
232
|
}
|
|
@@ -188,6 +247,30 @@ export class SchemaRepertoire {
|
|
|
188
247
|
this._learned.add( s.id )
|
|
189
248
|
}
|
|
190
249
|
}
|
|
250
|
+
|
|
251
|
+
/** Availability ledger encoded as `agency.availability` state entities (P2).
|
|
252
|
+
* Empty until a refusal lands, so the quiet path writes nothing. */
|
|
253
|
+
availabilityEntities(): EntityInput[] {
|
|
254
|
+
const out: EntityInput[] = []
|
|
255
|
+
for( const [ schema, a ] of this._availability )
|
|
256
|
+
out.push( availabilityEntity( schema, a.value, a.lastRefusedTick ) )
|
|
257
|
+
return out
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Rehydrate the availability ledger from state after a restore. Idempotent;
|
|
261
|
+
* keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
|
|
262
|
+
restoreAvailability( entities: ReadonlySimulationState['entities'] ): void {
|
|
263
|
+
for( const e of entities.values() ){
|
|
264
|
+
if( e.type !== AVAILABILITY_ENTITY_TYPE ) continue
|
|
265
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
266
|
+
const schema = typeof m['schema'] === 'string' ? m['schema'] : ''
|
|
267
|
+
if( !schema ) continue
|
|
268
|
+
const value = typeof m['value'] === 'number' ? m['value'] : 1
|
|
269
|
+
const tick = typeof m['lastRefusedTick'] === 'number' ? m['lastRefusedTick'] : 0
|
|
270
|
+
const prev = this._availability.get( schema )
|
|
271
|
+
if( !prev || value < prev.value ) this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
272
|
+
}
|
|
273
|
+
}
|
|
191
274
|
}
|
|
192
275
|
|
|
193
276
|
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -209,6 +292,23 @@ function clamp01( n: number ): number {
|
|
|
209
292
|
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
210
293
|
}
|
|
211
294
|
|
|
295
|
+
// ─── availability ⇄ state-entity codec (P2) ──────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/** State-entity type for a mirrored availability entry. */
|
|
298
|
+
export const AVAILABILITY_ENTITY_TYPE = 'agency.availability'
|
|
299
|
+
|
|
300
|
+
/** Stable entity id for a schema's availability mirror (idempotent re-writes). */
|
|
301
|
+
export function availabilityEntityId( schema: string ): string { return `agency-availability-${ schema }` }
|
|
302
|
+
|
|
303
|
+
/** Encode one availability entry as a state entity. */
|
|
304
|
+
function availabilityEntity( schema: string, value: number, lastRefusedTick: number ): EntityInput {
|
|
305
|
+
return {
|
|
306
|
+
id: availabilityEntityId( schema ),
|
|
307
|
+
type: AVAILABILITY_ENTITY_TYPE,
|
|
308
|
+
metadata: { schema, value, lastRefusedTick },
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
212
312
|
// ─── composite ⇄ state-entity codec (snapshot/replay) ────────────────────────
|
|
213
313
|
|
|
214
314
|
/** State-entity type for a mirrored composite template. */
|
|
@@ -242,7 +342,7 @@ function readSchema( m: Record<string, unknown> | undefined ): MotorSchema | und
|
|
|
242
342
|
const meta = m ?? {}
|
|
243
343
|
const id = typeof meta['id'] === 'string' ? meta['id'] as string : undefined
|
|
244
344
|
const kind = meta['kind']
|
|
245
|
-
if( !id || ( kind !== 'composite' && kind !== 'primitive'
|
|
345
|
+
if( !id || ( kind !== 'composite' && kind !== 'primitive') ) return undefined
|
|
246
346
|
return {
|
|
247
347
|
id,
|
|
248
348
|
kind,
|