@principles/host-runtime 0.10.3 → 0.10.4
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.
|
@@ -144,12 +144,23 @@ export type AdmitGovernanceSignalsResult = {
|
|
|
144
144
|
ok: true;
|
|
145
145
|
outcomes: readonly GovernanceAdmissionOutcome[];
|
|
146
146
|
} | Degradation;
|
|
147
|
+
/** Public generic intake stays Codex + explicit WorkBuddy only. */
|
|
148
|
+
export declare function admitGovernanceSignals(input: AdmitGovernanceSignalsInput): AdmitGovernanceSignalsResult;
|
|
149
|
+
/** Pure check: no DB, no filesystem, no classifier, no historical transcript. */
|
|
150
|
+
export declare function assessWorkbuddyLiveKeyword(text: string, sessionId: string): {
|
|
151
|
+
matched: boolean;
|
|
152
|
+
reason: 'matched' | 'not_a_signal' | 'prompt_too_large';
|
|
153
|
+
};
|
|
147
154
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* failing candidate never rolls back another's admission.
|
|
155
|
+
* Narrow native event gateway: validate true current user origin and flags.
|
|
156
|
+
* The ordinary generic admission API cannot bypass this WorkBuddy gate.
|
|
151
157
|
*/
|
|
152
|
-
export declare function
|
|
158
|
+
export declare function admitTrustedWorkbuddyLiveCorrection(input: {
|
|
159
|
+
workspaceDir: string;
|
|
160
|
+
rawEvent: unknown;
|
|
161
|
+
now?: Date;
|
|
162
|
+
databaseFactory?: ObservationDatabaseFactory;
|
|
163
|
+
}): AdmitGovernanceSignalsResult;
|
|
153
164
|
export interface EnsureGovernanceTaskInput {
|
|
154
165
|
readonly workspaceDir: string;
|
|
155
166
|
readonly logicalObservationKey: string;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
import fs from 'node:fs';
|
|
35
35
|
import path from 'node:path';
|
|
36
36
|
import Database from 'better-sqlite3';
|
|
37
|
-
import { collectSync, CORRECTION_SEED_KEYWORDS, buildToolFailureObservation, evaluateTriage, evaluateTriggerController, resolveSourceKind, PainToPrincipleService, PrincipleTreeLedgerAdapter, RuntimeStateManager, createDiagnosticianTaskId, disposePainSignalBridgesForWorkspace, sanitizeString, boundCorrectionEvidenceText, MAX_EVIDENCE_VALUE_CHARS, } from '@principles/core/runtime-v2';
|
|
37
|
+
import { collectSync, computeFeatureFlagsFromConfig, CORRECTION_SEED_KEYWORDS, buildToolFailureObservation, evaluateTriage, evaluateTriggerController, resolveSourceKind, PainToPrincipleService, PrincipleTreeLedgerAdapter, RuntimeStateManager, createDiagnosticianTaskId, disposePainSignalBridgesForWorkspace, sanitizeString, boundCorrectionEvidenceText, MAX_EVIDENCE_VALUE_CHARS, } from '@principles/core/runtime-v2';
|
|
38
38
|
import { deriveProductionCorrectionPainIdentity, deriveProductionToolPainIdentity, hasProductionPainSchema, PRODUCTION_WRITE_TOOLS, } from './production-pain-evidence.js';
|
|
39
39
|
import { promoteGovernanceEvidence, ensureGovernanceSchema } from './governance-observation-store.js';
|
|
40
40
|
import { loadPdConfigForPlugin } from './pd-config.js';
|
|
@@ -432,7 +432,8 @@ function toolFailureTaskPayload(fields) {
|
|
|
432
432
|
};
|
|
433
433
|
return JSON.stringify(payload);
|
|
434
434
|
}
|
|
435
|
-
function taskSubmissionPayload(candidate, workspaceDir,
|
|
435
|
+
function taskSubmissionPayload(candidate, workspaceDir, details) {
|
|
436
|
+
const { reason, canonicalPainId } = details;
|
|
436
437
|
if (candidate.kind === 'user_correction') {
|
|
437
438
|
// P1-1 privacy (review round 2): sanitize the FULL text before any
|
|
438
439
|
// truncation — a token crossing the 200-char boundary must be redacted, not
|
|
@@ -446,9 +447,14 @@ function taskSubmissionPayload(candidate, workspaceDir, reason) {
|
|
|
446
447
|
hostKind: candidate.hostKind,
|
|
447
448
|
// WorkBuddy explicit intake has no transcript observation to promote.
|
|
448
449
|
// The classified Owner statement is its input basis, not a pseudo-trace.
|
|
449
|
-
evidence: candidate.hostKind === 'workbuddy'
|
|
450
|
-
? []
|
|
451
|
-
|
|
450
|
+
evidence: candidate.hostKind === 'workbuddy'
|
|
451
|
+
? candidate.explicitOwnerStatement === true ? [] : [{
|
|
452
|
+
// WorkBuddy has no governance_observation, anchor to the canonical
|
|
453
|
+
// Pain persisted in this same trajectory.db transaction.
|
|
454
|
+
sourceRef: 'pain_event:' + (canonicalPainId ?? ''),
|
|
455
|
+
note: sanitizeString(candidate.text, workspaceDir),
|
|
456
|
+
}]
|
|
457
|
+
: [{ sourceRef: 'governance_observation:' + candidate.logicalObservationKey, note: sanitizeString(candidate.text, workspaceDir) }],
|
|
452
458
|
// SPEC-CODEX-PAIN-CLOSURE-001 CR-02 (merged from main): an explicit
|
|
453
459
|
// Owner statement rides as classified correction evidence — never
|
|
454
460
|
// summarized by an AI, never inside evidence[] pseudo-trace entries.
|
|
@@ -544,7 +550,7 @@ function admitCorrection({ db, candidate, workspaceDir, now, nowIso, keywordStor
|
|
|
544
550
|
// detection, so `detection` is optional — no ruleVersion exists for them.
|
|
545
551
|
ruleVersion: detection?.ruleVersion ?? null,
|
|
546
552
|
reason,
|
|
547
|
-
payloadJson: taskSubmissionPayload(candidate, workspaceDir, reason),
|
|
553
|
+
payloadJson: taskSubmissionPayload(candidate, workspaceDir, { reason, canonicalPainId: painId }),
|
|
548
554
|
nowIso,
|
|
549
555
|
});
|
|
550
556
|
})();
|
|
@@ -637,7 +643,7 @@ function admitToolFailure({ db, candidate, workspaceDir, nowIso }) {
|
|
|
637
643
|
canonicalPainId: painId,
|
|
638
644
|
ruleVersion: null,
|
|
639
645
|
reason: sanitizeString(reason, workspaceDir),
|
|
640
|
-
payloadJson: taskSubmissionPayload(candidate, workspaceDir, reason),
|
|
646
|
+
payloadJson: taskSubmissionPayload(candidate, workspaceDir, { reason }),
|
|
641
647
|
nowIso,
|
|
642
648
|
});
|
|
643
649
|
}
|
|
@@ -650,8 +656,8 @@ function admitToolFailure({ db, candidate, workspaceDir, nowIso }) {
|
|
|
650
656
|
}
|
|
651
657
|
return { disposition: 'admitted', kind: 'tool_failure', logicalObservationKey: candidate.logicalObservationKey, canonicalPainId: painId, duplicate, ruleVersion: null };
|
|
652
658
|
}
|
|
653
|
-
function admitOne({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDegradation }) {
|
|
654
|
-
if (candidate.hostKind !== 'codex' && !(candidate.hostKind === 'workbuddy' && candidate.kind === 'user_correction' && candidate.explicitOwnerStatement === true)) {
|
|
659
|
+
function admitOne({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDegradation, trustedWorkbuddyLive }) {
|
|
660
|
+
if (candidate.hostKind !== 'codex' && !(candidate.hostKind === 'workbuddy' && candidate.kind === 'user_correction' && (candidate.explicitOwnerStatement === true || trustedWorkbuddyLive === true))) {
|
|
655
661
|
return { ok: false, reason: 'unsupported_host_kind', nextAction: 'governance signal admission accepts Codex observations or explicit WorkBuddy Owner statements' };
|
|
656
662
|
}
|
|
657
663
|
const marker = db.prepare('SELECT * FROM governance_signal_admissions WHERE logical_observation_key = ?').get(candidate.logicalObservationKey);
|
|
@@ -676,7 +682,7 @@ function admitOne({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDe
|
|
|
676
682
|
* trajectory.db transaction (marker + pain row + quota together), so one
|
|
677
683
|
* failing candidate never rolls back another's admission.
|
|
678
684
|
*/
|
|
679
|
-
|
|
685
|
+
function admitGovernanceSignalsInternal(input, trustedWorkbuddyLive = false) {
|
|
680
686
|
const opened = openAdmissionStore(input.workspaceDir, input.databaseFactory);
|
|
681
687
|
if (!('db' in opened))
|
|
682
688
|
return opened;
|
|
@@ -691,7 +697,7 @@ export function admitGovernanceSignals(input) {
|
|
|
691
697
|
const outcomes = [];
|
|
692
698
|
try {
|
|
693
699
|
for (const candidate of input.candidates) {
|
|
694
|
-
outcomes.push(admitOne({ db, candidate, workspaceDir, now, nowIso, ...(input.keywordStore ? { keywordStore: input.keywordStore } : {}), ...(input.onDegradation ? { onDegradation: input.onDegradation } : {}) }));
|
|
700
|
+
outcomes.push(admitOne({ db, candidate, workspaceDir, now, nowIso, ...(input.keywordStore ? { keywordStore: input.keywordStore } : {}), ...(input.onDegradation ? { onDegradation: input.onDegradation } : {}), trustedWorkbuddyLive }));
|
|
695
701
|
}
|
|
696
702
|
return { ok: true, outcomes };
|
|
697
703
|
}
|
|
@@ -703,6 +709,87 @@ export function admitGovernanceSignals(input) {
|
|
|
703
709
|
close();
|
|
704
710
|
}
|
|
705
711
|
}
|
|
712
|
+
/** Public generic intake stays Codex + explicit WorkBuddy only. */
|
|
713
|
+
export function admitGovernanceSignals(input) {
|
|
714
|
+
return admitGovernanceSignalsInternal(input);
|
|
715
|
+
}
|
|
716
|
+
/** P0: a four-term projection of existing canonical high-precision seeds. */
|
|
717
|
+
const WORKBUDDY_AUTO_TERMS = new Set(['这是错的', '不要自作主张', '不应该这么做', '又搞错']);
|
|
718
|
+
const WORKBUDDY_MAX_PROMPT_BYTES = 16 * 1024;
|
|
719
|
+
function workbuddyP0KeywordStore() {
|
|
720
|
+
const seed = buildSharedSeedKeywordStore();
|
|
721
|
+
const terms = {};
|
|
722
|
+
for (const [term, def] of Object.entries(seed.terms)) {
|
|
723
|
+
if (WORKBUDDY_AUTO_TERMS.has(term) && def.category === 'correction' && def.precision === 'high') {
|
|
724
|
+
terms[term] = def;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return { version: seed.version, terms };
|
|
728
|
+
}
|
|
729
|
+
/** Pure check: no DB, no filesystem, no classifier, no historical transcript. */
|
|
730
|
+
export function assessWorkbuddyLiveKeyword(text, sessionId) {
|
|
731
|
+
if (Buffer.byteLength(text, 'utf8') > WORKBUDDY_MAX_PROMPT_BYTES) {
|
|
732
|
+
return { matched: false, reason: 'prompt_too_large' };
|
|
733
|
+
}
|
|
734
|
+
const store = workbuddyP0KeywordStore();
|
|
735
|
+
// Prefer precision to recall: reject quoted/embedded/reported phrases.
|
|
736
|
+
if (!Object.keys(store.terms).some(term => text.trimStart().toLowerCase().startsWith(term.toLowerCase()))) {
|
|
737
|
+
return { matched: false, reason: 'not_a_signal' };
|
|
738
|
+
}
|
|
739
|
+
const out = collectSync(text, sessionId, store, SYNC_DETECTION_CONFIG, new Date().toISOString());
|
|
740
|
+
const matched = out.isSignal && out.strength === 'STRONG' &&
|
|
741
|
+
out.type === 'correction' && out.matchedPrecision === 'high';
|
|
742
|
+
return { matched, reason: matched ? 'matched' : 'not_a_signal' };
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Narrow native event gateway: validate true current user origin and flags.
|
|
746
|
+
* The ordinary generic admission API cannot bypass this WorkBuddy gate.
|
|
747
|
+
*/
|
|
748
|
+
export function admitTrustedWorkbuddyLiveCorrection(input) {
|
|
749
|
+
const raw = input.rawEvent;
|
|
750
|
+
if (!isRecord(raw) || own(raw, 'hook_event_name') !== 'UserPromptSubmit' ||
|
|
751
|
+
own(raw, 'is_internal_continuation') !== false) {
|
|
752
|
+
return { ok: false, reason: 'native_current_user_unverified', nextAction: 'Require a real current WorkBuddy UserPromptSubmit event.' };
|
|
753
|
+
}
|
|
754
|
+
const sessionId = own(raw, 'session_id');
|
|
755
|
+
const generationId = own(raw, 'generation_id');
|
|
756
|
+
const prompt = own(raw, 'prompt');
|
|
757
|
+
if (typeof sessionId !== 'string' || !sessionId.trim() ||
|
|
758
|
+
typeof generationId !== 'string' || !generationId.trim() ||
|
|
759
|
+
typeof prompt !== 'string') {
|
|
760
|
+
return { ok: false, reason: 'native_identity_invalid', nextAction: 'Require nonblank native session_id/generation_id and prompt.' };
|
|
761
|
+
}
|
|
762
|
+
const config = loadPdConfigForPlugin(input.workspaceDir);
|
|
763
|
+
if (!config.ok)
|
|
764
|
+
return { ok: false, reason: 'workbuddy_config_invalid', nextAction: 'Repair WorkBuddy governance workspace config.' };
|
|
765
|
+
const { flags } = computeFeatureFlagsFromConfig(config.effective);
|
|
766
|
+
if (flags['host.workbuddy']?.enabled !== true || flags.workbuddy_live_keyword_pain?.enabled !== true) {
|
|
767
|
+
return { ok: false, reason: 'workbuddy_auto_disabled', nextAction: 'Explicitly enable both WorkBuddy host and live keyword Pain.' };
|
|
768
|
+
}
|
|
769
|
+
if (prompt.trimStart().startsWith('PD记录痛苦')) {
|
|
770
|
+
return { ok: false, reason: 'explicit_owner_statement_reserved', nextAction: 'Use original Owner intake; no keyword fallback.' };
|
|
771
|
+
}
|
|
772
|
+
if (!assessWorkbuddyLiveKeyword(prompt, sessionId).matched) {
|
|
773
|
+
return { ok: true, outcomes: [] };
|
|
774
|
+
}
|
|
775
|
+
const nativeIdentity = JSON.stringify(['workbuddy', sessionId, generationId]);
|
|
776
|
+
return admitGovernanceSignalsInternal({
|
|
777
|
+
workspaceDir: input.workspaceDir,
|
|
778
|
+
candidates: [{
|
|
779
|
+
kind: 'user_correction',
|
|
780
|
+
hostKind: 'workbuddy',
|
|
781
|
+
logicalObservationKey: nativeIdentity + '|user',
|
|
782
|
+
rolloutIdentity: JSON.stringify(['workbuddy', sessionId]),
|
|
783
|
+
rootSessionId: sessionId,
|
|
784
|
+
hostTurnId: nativeIdentity,
|
|
785
|
+
text: prompt,
|
|
786
|
+
observedAt: (input.now ?? new Date()).toISOString(),
|
|
787
|
+
}],
|
|
788
|
+
keywordStore: workbuddyP0KeywordStore(),
|
|
789
|
+
...(input.now ? { now: input.now } : {}),
|
|
790
|
+
...(input.databaseFactory ? { databaseFactory: input.databaseFactory } : {}),
|
|
791
|
+
}, true);
|
|
792
|
+
}
|
|
706
793
|
// ─── Diagnostician continuation (one pain → exactly one pending task) ───────
|
|
707
794
|
function isGovernanceTaskPayload(value) {
|
|
708
795
|
if (!isRecord(value))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/host-runtime",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
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.
|
|
34
|
+
"@principles/core": "^1.294.1",
|
|
35
35
|
"@principles/install-layout": "^0.4.0",
|
|
36
36
|
"better-sqlite3": "^13.0.3",
|
|
37
37
|
"js-yaml": "^5.4.3"
|