@principles/host-runtime 0.12.2 → 0.13.1

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/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { isHostEvent, isHostEventResult, } from '@principles/core/host';
3
- import { getPromptModelCatalog, resolvePromptBudget } from '@principles/core/runtime-v2';
3
+ import { getPromptModelCatalog, operatorDeclaredContextWindow, resolvePromptBudget } from '@principles/core/runtime-v2';
4
+ import { loadPdConfigForPlugin } from './pd-config.js';
4
5
  import { buildActivePrinciplePromptContext } from './active-principle-prompt.js';
5
6
  import { createProductionRuleHostGate } from './production-rulehost-gate.js';
6
7
  import { createProductionPainEvidenceHandler } from './production-pain-evidence.js';
@@ -212,6 +213,19 @@ export function createProductionHostRuntime(options = {}) {
212
213
  // reports `legacy_fifo_prefix_v1` honestly. The selector's fair-rotation
213
214
  // capability is retained and stays reachable for callers that DO hold a
214
215
  // legitimate advancing round authority.
216
+ // PRI-1020: the Owner's declared window (.pd/config.yaml promptBudget)
217
+ // is tier 0 of the budget chain — resolved per host and fed into the
218
+ // SAME single resolver below. One extra sync config read per prompt
219
+ // build (the candidates reader loads it independently): deliberate, so
220
+ // a config flip applies on the next turn with no cache to invalidate.
221
+ // Malformed config degrades to defaults (no declaration) and is already
222
+ // surfaced as config_invalid warnings by readPromptActivationCandidates.
223
+ const promptBudgetHostKind = options.hostKind === 'openclaw' || options.hostKind === 'codex' || options.hostKind === 'workbuddy'
224
+ ? options.hostKind
225
+ : undefined;
226
+ const declaredWindow = promptBudgetHostKind === undefined
227
+ ? undefined
228
+ : operatorDeclaredContextWindow(loadPdConfigForPlugin(event.context.workspaceDir).effective.config.promptBudget, promptBudgetHostKind);
215
229
  const prompt = await buildActivePrinciplePromptContext({
216
230
  workspaceDir: event.context.workspaceDir,
217
231
  excludePrincipleIds: options.promptExcludePrincipleIds?.(event),
@@ -221,6 +235,8 @@ export function createProductionHostRuntime(options = {}) {
221
235
  // Codex+WorkBuddy model id) against the installed local Pi AI
222
236
  // catalog — no network, no LLM call. Missing/untrusted facts
223
237
  // resolve to the legacy fixed budget with an observable reason.
238
+ // PRI-1020: an Owner declaration outranks all of these (tier 0).
239
+ ...(declaredWindow !== undefined ? { operatorDeclared: { contextWindowTokens: declaredWindow } } : {}),
224
240
  hostCapacity: event.context.modelContext === undefined
225
241
  ? undefined
226
242
  : {
@@ -701,6 +701,10 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
701
701
  // identity on the live production chain. listForCandidate reads
702
702
  // the ledger from disk per call — no cross-cycle staleness.
703
703
  ledgerIdentity: {
704
+ resolveForCandidate: async (candidateId) => {
705
+ const candidate = await stateManager.getCandidate(candidateId);
706
+ return new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }).resolveCandidatePrincipleIdentity(candidateId, candidate?.rawRecommendationKind);
707
+ },
704
708
  // PRI-915: availability first — an unreadable ledger must not
705
709
  // read as an unresolved chain.
706
710
  isAvailable: () => new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }).isAvailable(),
@@ -711,6 +715,10 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
711
715
  case 'artificer':
712
716
  runner = new ArtificerRunner({
713
717
  stateManager, runtimeAdapter: adapter, eventEmitter: storeEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultArtificerValidator(),
718
+ ledgerIdentity: { resolveForCandidate: async (candidateId) => {
719
+ const candidate = await stateManager.getCandidate(candidateId);
720
+ return new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }).resolveCandidatePrincipleIdentity(candidateId, candidate?.rawRecommendationKind);
721
+ } },
714
722
  // PRI-982 (SPEC-PD-IAER-001 FR-02): the preflight-assembled Owner
715
723
  // evidence pack — present only when the authoritative channel is
716
724
  // code_tool_hook AND the readiness gate returned ready (same
@@ -753,6 +761,10 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
753
761
  // PRI-915: availability first — an unreadable ledger must surface
754
762
  // as identity_source_unavailable, not as invalid_identity drift.
755
763
  ledgerIdentity: {
764
+ resolveForCandidate: async (candidateId) => {
765
+ const candidate = await stateManager.getCandidate(candidateId);
766
+ return new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }).resolveCandidatePrincipleIdentity(candidateId, candidate?.rawRecommendationKind);
767
+ },
756
768
  isAvailable: () => new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }).isAvailable(),
757
769
  hasPrinciple: (principleId) => new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }).hasPrinciple(principleId),
758
770
  },
@@ -70,6 +70,10 @@ export async function dispatchRolloutActivation(workspaceDir, input, options = {
70
70
  // used to mint the principle UUID); candidate lineage reuses the artifact
71
71
  // read model plus raw diagnostic_json reads.
72
72
  const ledgerIdentity = {
73
+ getCandidateKind: (candidateId) => {
74
+ const row = connection.getDb().prepare('SELECT recommendation_kind FROM principle_candidates WHERE candidate_id = ?').get(candidateId);
75
+ return row !== null && typeof row === 'object' && Object.hasOwn(row, 'recommendation_kind') ? Reflect.get(row, 'recommendation_kind') : undefined;
76
+ },
73
77
  ledger: new PrincipleTreeLedgerAdapter({ stateDir: `${workspaceDir}/.state` }),
74
78
  getArtifactById: artifactReadModel.getArtifactById,
75
79
  getTaskDiagnosticJson: (taskId) => {
@@ -1,4 +1,4 @@
1
- import { PROMPT_BUDGET_POLICY_VERSION, type PromptBudgetCapacitySource, type PromptBudgetCapacityStatus, type PromptBudgetFallbackReason, type PromptBudgetResolution } from '@principles/core/runtime-v2';
1
+ import { PROMPT_BUDGET_POLICY_VERSION, type PromptBudgetCapacitySource, type PromptBudgetCapacityStatus, type PromptBudgetFallbackReason, type PromptBudgetResolution, type PromptBudgetConfig } from '@principles/core/runtime-v2';
2
2
  import { type PromptInjectionTargetHost } from './prompt-injection-projection.js';
3
3
  /**
4
4
  * PRI-1017: runtime budget OBSERVATION read model over the EXISTING
@@ -174,3 +174,12 @@ export declare function selectConsoleRuntimeBudgetFacts(input: {
174
174
  * rides events or console payloads.
175
175
  */
176
176
  export declare function runtimeBudgetObservationToResolution(observation: RuntimePromptBudgetObservation): PromptBudgetResolution;
177
+ /**
178
+ * PRI-1020: the Owner's declared window as a budget resolution, for console
179
+ * surfaces with NO fresh runtime observation. Goes through the same single
180
+ * resolver (tier 0) so an injected forecast and the console fallback can
181
+ * never disagree about what a declaration means. Undefined when the
182
+ * workspace declares nothing — the caller then keeps the historical fixed
183
+ * fallback.
184
+ */
185
+ export declare function operatorDeclaredBudgetResolution(promptBudget: PromptBudgetConfig | undefined, hostKind: PromptInjectionTargetHost): PromptBudgetResolution | undefined;
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { PROMPT_BUDGET_POLICY_VERSION, RUNTIME_V2_PRINCIPLE_BUDGET, isValidContextWindowTokens, promptBudgetCharsForContextWindow, } from '@principles/core/runtime-v2';
3
+ import { PROMPT_BUDGET_POLICY_VERSION, RUNTIME_V2_PRINCIPLE_BUDGET, isValidContextWindowTokens, promptBudgetCharsForContextWindow, resolvePromptBudget, operatorDeclaredContextWindow, } from '@principles/core/runtime-v2';
4
4
  import { isPromptInjectionTargetHost, } from './prompt-injection-projection.js';
5
5
  /**
6
6
  * PRI-1017: runtime budget OBSERVATION read model over the EXISTING
@@ -104,6 +104,7 @@ function parseObservedAt(value) {
104
104
  return Number.isFinite(parsed) ? parsed : null;
105
105
  }
106
106
  const BUDGET_CAPACITY_SOURCES = new Set([
107
+ 'operator_declared',
107
108
  'host_effective_window',
108
109
  'pi_catalog_exact',
109
110
  'legacy_fallback',
@@ -177,7 +178,11 @@ function narrowObservation(parsed, workspaceDir, nowMs) {
177
178
  // formula derives from it; the legacy fallback needs the legacy budget, no
178
179
  // window, and a bounded reason. Anything else is not a trustworthy fact.
179
180
  if (rawStatus === 'confirmed') {
180
- if (rawSource !== 'host_effective_window' && rawSource !== 'pi_catalog_exact')
181
+ // PRI-1020: operator_declared joins the confirmed set — a declared window
182
+ // is a real window (the Owner's explicit cap), so its observations carry
183
+ // the window and the exact formula budget like the other confirmed
184
+ // sources.
185
+ if (rawSource !== 'operator_declared' && rawSource !== 'host_effective_window' && rawSource !== 'pi_catalog_exact')
181
186
  return null;
182
187
  if (!isValidContextWindowTokens(rawWindow))
183
188
  return null;
@@ -447,3 +452,17 @@ export function runtimeBudgetObservationToResolution(observation) {
447
452
  ...(observation.fallbackReason !== null ? { fallbackReason: observation.fallbackReason } : {}),
448
453
  };
449
454
  }
455
+ /**
456
+ * PRI-1020: the Owner's declared window as a budget resolution, for console
457
+ * surfaces with NO fresh runtime observation. Goes through the same single
458
+ * resolver (tier 0) so an injected forecast and the console fallback can
459
+ * never disagree about what a declaration means. Undefined when the
460
+ * workspace declares nothing — the caller then keeps the historical fixed
461
+ * fallback.
462
+ */
463
+ export function operatorDeclaredBudgetResolution(promptBudget, hostKind) {
464
+ const declaredWindow = operatorDeclaredContextWindow(promptBudget, hostKind);
465
+ if (declaredWindow === undefined)
466
+ return undefined;
467
+ return resolvePromptBudget({ operatorDeclared: { contextWindowTokens: declaredWindow } });
468
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/host-runtime",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "Shared host-neutral orchestration for Principles Disciple MVP-Core hook paths.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,7 +31,7 @@
31
31
  "lint": "eslint \"src/**/*.ts\""
32
32
  },
33
33
  "dependencies": {
34
- "@principles/core": "^1.295.3",
34
+ "@principles/core": "^1.296.2",
35
35
  "@principles/install-layout": "^0.4.0",
36
36
  "better-sqlite3": "^13.0.3",
37
37
  "js-yaml": "^5.4.3"