@principles/host-runtime 0.1.0 → 0.1.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.
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { buildRuleHostAction, estimateLineChanges, mergeDecisions, SqliteConnection, UNAVAILABLE_RULE_CONTEXT, validateRuleContextV2, validateRuleHostResult, } from '@principles/core/runtime-v2';
4
+ import { scanRetiredContractSymbols } from './legacy-rule-contract-symbols.js';
4
5
  import { createNodeRuleImplementationRuntime, MAX_ACTIVE_RULES, RULE_BATCH_SOURCE_BYTES, RULE_SOURCE_BYTES, } from './rule-implementation-runtime.js';
5
6
  const WARNING_LIMIT = 500;
6
7
  const MAX_WARNINGS = 16;
@@ -76,7 +77,7 @@ export function createProductionRuleHostGate(options = {}) {
76
77
  if (action.normalizedPath === null)
77
78
  return { decision: 'allow', source: event.source, metadata: { evaluatedLiveRules: 0 } };
78
79
  let context;
79
- let enrichment = { currentGfi: 0, recentThinking: false, epTier: 0, bashRisk: 'unknown' };
80
+ let enrichment = { currentGfi: 0, epTier: 0, bashRisk: 'unknown' };
80
81
  const request = {
81
82
  workspaceDir: event.context.workspaceDir, sessionId: event.context.sessionId,
82
83
  targetPath: action.normalizedPath, toolName: input.toolName, rawPayload: event.rawPayload,
@@ -85,9 +86,9 @@ export function createProductionRuleHostGate(options = {}) {
85
86
  try {
86
87
  const candidate = await withinGateDeadline(options.ruleInputEnrichmentProvider(request), startedAt);
87
88
  if (isRecord(candidate) && typeof candidate.currentGfi === 'number' && Number.isFinite(candidate.currentGfi)
88
- && typeof candidate.recentThinking === 'boolean' && typeof candidate.epTier === 'number' && Number.isFinite(candidate.epTier)
89
+ && typeof candidate.epTier === 'number' && Number.isFinite(candidate.epTier)
89
90
  && (candidate.bashRisk === 'safe' || candidate.bashRisk === 'normal' || candidate.bashRisk === 'dangerous' || candidate.bashRisk === 'unknown')) {
90
- enrichment = { currentGfi: candidate.currentGfi, recentThinking: candidate.recentThinking, epTier: candidate.epTier, bashRisk: candidate.bashRisk };
91
+ enrichment = { currentGfi: candidate.currentGfi, epTier: candidate.epTier, bashRisk: candidate.bashRisk };
91
92
  }
92
93
  else
93
94
  addWarning(warnings, 'rule_input_enrichment_invalid', 'repair the host enrichment provider');
@@ -127,11 +128,25 @@ export function createProductionRuleHostGate(options = {}) {
127
128
  const connection = new SqliteConnection({ workspaceDir: event.context.workspaceDir, readonly: true, bootstrapIfMissing: false });
128
129
  const candidates = [];
129
130
  try {
131
+ const globalPause = connection.getDb().prepare(`
132
+ SELECT pause_id FROM global_rulecode_pauses WHERE status = 'paused' LIMIT 1
133
+ `).get();
134
+ if (globalPause !== undefined) {
135
+ return {
136
+ decision: 'allow',
137
+ source: event.source,
138
+ warnings: [boundedWarning('global_rulecode_pause_active', 'review the incident in the Owner Console before releasing the global pause')],
139
+ metadata: { evaluatedLiveRules: 0 },
140
+ };
141
+ }
130
142
  const rows = connection.getDb().prepare(`
131
143
  SELECT a.activation_id, a.artifact_id, a.target_ref, a.action,
144
+ c.enforcement, c.isolation_decision_id,
132
145
  p.source_rule_id, p.source_principle_id,
133
146
  length(CAST(p.content_json AS BLOB)) AS content_bytes
134
- FROM activations a JOIN pi_artifacts p ON a.artifact_id = p.artifact_id
147
+ FROM activations a
148
+ JOIN pi_artifacts p ON a.artifact_id = p.artifact_id
149
+ LEFT JOIN activation_control_states c ON a.activation_id = c.activation_id
135
150
  WHERE a.channel = 'code_tool_hook' AND a.deactivated_at IS NULL
136
151
  ORDER BY a.activated_at ASC
137
152
  LIMIT ?
@@ -158,6 +173,15 @@ export function createProductionRuleHostGate(options = {}) {
158
173
  addWarning(warnings, 'activation_row_invalid', 'deactivate and recreate the malformed activation');
159
174
  continue;
160
175
  }
176
+ const activationId = typeof row.activation_id === 'string' ? row.activation_id : 'unknown';
177
+ if (row.enforcement === 'safety_isolated') {
178
+ addWarning(warnings, `activation_safety_isolated: ${activationId}`, 'recover the activation to shadow after reviewing safety evidence');
179
+ continue;
180
+ }
181
+ if (row.enforcement !== 'eligible') {
182
+ addWarning(warnings, `activation_control_state_invalid: ${activationId}`, 'repair the activation control state before RuleCode enforcement');
183
+ continue;
184
+ }
161
185
  const group = groups.get(row.target_ref) ?? [];
162
186
  group.push(row);
163
187
  groups.set(row.target_ref, group);
@@ -216,6 +240,23 @@ export function createProductionRuleHostGate(options = {}) {
216
240
  }
217
241
  const ruleId = typeof content.ruleId === 'string' ? content.ruleId : typeof row.source_rule_id === 'string' ? row.source_rule_id : artifactId;
218
242
  const principleId = typeof content.principleId === 'string' ? content.principleId : typeof row.source_principle_id === 'string' ? row.source_principle_id : ruleId;
243
+ // Retired-contract backstop (2026-08-19; host-liveness correction
244
+ // 2026-08-21):
245
+ // persisted LIVE RuleCode that references removed RuleHost
246
+ // contract symbols can never run safely against the new contract
247
+ // — reads silently resolve to undefined and change owner-approved
248
+ // behavior. The incompatible activation is therefore skipped and
249
+ // surfaced with an actionable warning. It must not turn a runtime
250
+ // compatibility defect into a host-wide deny; healthy sibling
251
+ // rules continue to evaluate. The scan is a local copy (see
252
+ // legacy-rule-contract-symbols.ts) so the published bundle keeps
253
+ // working against the currently published core.
254
+ const retiredSymbols = scanRetiredContractSymbols(content.implementationCode);
255
+ if (retiredSymbols.length > 0) {
256
+ const nextAction = 'migrate the RuleCode off the retired contract symbols or deactivate the activation, then re-approve a migrated rule';
257
+ addWarning(warnings, `legacy_rule_contract_dependency: ${retiredSymbols.join(', ')} (activation=${activationId})`, nextAction);
258
+ continue;
259
+ }
219
260
  const fallbackMeta = { name: activationId, version: '1', ruleId, coversCondition: 'all' };
220
261
  candidates.push({ implId: activationId, ruleId, principleId, meta: isRuleMeta(content.meta) ? content.meta : fallbackMeta, source: content.implementationCode });
221
262
  }
@@ -226,8 +267,8 @@ export function createProductionRuleHostGate(options = {}) {
226
267
  }
227
268
  const hostInput = {
228
269
  action,
229
- workspace: { isRiskPath: false, planStatus: 'NONE', hasPlanFile: false },
230
- session: { sessionId: event.context.sessionId, currentGfi: enrichment.currentGfi, recentThinking: enrichment.recentThinking },
270
+ workspace: { isRiskPath: false },
271
+ session: { sessionId: event.context.sessionId, currentGfi: enrichment.currentGfi },
231
272
  evolution: { epTier: enrichment.epTier },
232
273
  derived: { estimatedLineChanges: estimateLineChanges({ toolName: input.toolName, params: input.params }), bashRisk: enrichment.bashRisk },
233
274
  ...(context ? { context } : {}),
@@ -37,8 +37,6 @@ try {
37
37
  ' getToolName: () => __pdCallInput.action.toolName,' +
38
38
  ' getEstimatedLineChanges: () => __pdCallInput.derived.estimatedLineChanges,' +
39
39
  ' getBashRisk: () => __pdCallInput.derived.bashRisk,' +
40
- ' hasPlanFile: () => __pdCallInput.workspace.hasPlanFile,' +
41
- ' getPlanStatus: () => __pdCallInput.workspace.planStatus,' +
42
40
  ' getEpTier: () => __pdCallInput.evolution.epTier' +
43
41
  '});', { filename: rule.filename + '.bootstrap' }
44
42
  ).runInContext(context, { timeout: 1000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/host-runtime",
3
- "version": "0.1.0",
3
+ "version": "0.1.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",
@@ -22,16 +22,21 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@principles/core": "^1.74.1",
25
+ "@principles/install-layout": "0.1.0",
25
26
  "better-sqlite3": "^13.0.3",
26
27
  "js-yaml": "^5.2.0"
27
28
  },
28
29
  "devDependencies": {
29
- "@types/better-sqlite3": "^7.6.13",
30
+ "@types/better-sqlite3": "^9.6.0",
30
31
  "@types/node": "^26.1.2",
31
32
  "@types/js-yaml": "^4.0.9",
32
33
  "typescript": "^7.0.2",
33
34
  "vite": "^8.0.16",
34
35
  "vitest": "^4.1.10"
35
36
  },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/csuzngjh/principles.git"
40
+ },
36
41
  "license": "MIT"
37
42
  }