phaedo-mcp 0.2.1 → 0.3.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/NOTICE +17 -0
- package/README.md +51 -9
- package/action-binding.js +82 -0
- package/agreement.js +38 -0
- package/ap2.js +214 -0
- package/autonomy-promotion.js +280 -0
- package/build-package.mjs +25 -10
- package/capture-a11y.js +116 -0
- package/capture-local.js +85 -0
- package/capture-sync.js +99 -0
- package/consult-core.js +81 -19
- package/consult-policy.js +427 -33
- package/consult.js +25 -3
- package/delegation-sync.js +89 -0
- package/escalation-policy.sample.json +13 -0
- package/escalation.js +383 -0
- package/fingerprint-source.js +78 -4
- package/health.js +54 -0
- package/hooks/session-end.mjs +240 -0
- package/install.mjs +164 -21
- package/lib/capture-home.js +46 -0
- package/lib/domain.js +101 -0
- package/lib/is-main.js +40 -0
- package/lib/phaedo-crypto.js +17 -1
- package/package.json +26 -6
- package/pairing.js +5 -1
- package/phone-source.js +92 -12
- package/projection.js +4 -1
- package/pull.js +19 -3
- package/receipts-sync.js +197 -0
- package/receipts.js +69 -16
- package/server.js +305 -15
- package/server.json +4 -4
- package/signing-audit.js +111 -0
- package/signing-request.js +217 -0
- package/signing.js +176 -0
- package/vendor/authored-source.js +250 -0
- package/vendor/capabilities.js +203 -0
- package/vendor/capture/capture-core.js +67 -0
- package/vendor/capture/detectors.js +287 -0
- package/vendor/capture/exchanges.js +77 -0
- package/vendor/capture/model-detect.js +195 -0
- package/vendor/capture/owner.js +35 -0
- package/vendor/capture/package.json +4 -0
- package/vendor/capture/source-id.js +56 -0
- package/vendor/capture/state.js +148 -0
- package/vendor/capture/transcript.js +153 -0
- package/vendor/commitments.js +169 -0
- package/vendor/context-block.js +251 -18
- package/vendor/envelope.js +50 -4
- package/vendor/extraction/capture-combine.js +160 -0
- package/vendor/extraction/catalog.js +232 -0
- package/vendor/extraction/delta-lift.js +109 -0
- package/vendor/extraction/fingerprint-update.js +227 -0
- package/vendor/extraction/package.json +4 -0
- package/vendor/extraction/persist.js +40 -0
- package/vendor/extraction/reconcile.js +269 -0
- package/vendor/extraction/resolve.js +178 -0
- package/vendor/extraction/rule-promotion.js +137 -0
- package/vendor/kdf.js +20 -1
- package/vendor/protocol.js +7 -0
- package/vendored-files.mjs +82 -0
package/consult-core.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
import { PhaedoError } from './errors.js';
|
|
18
18
|
import { loadPolicies, applyPolicies, matchAuthorization } from './consult-policy.js';
|
|
19
|
+
import { normalizeActionBinding, isBound } from './action-binding.js';
|
|
20
|
+
import { normalizeDomain, canonicalDomain } from './lib/domain.js';
|
|
19
21
|
|
|
20
22
|
export const CONSULTATION_TYPES = [
|
|
21
23
|
'action_approval',
|
|
@@ -367,14 +369,31 @@ export function normalizeRequest(request) {
|
|
|
367
369
|
}
|
|
368
370
|
const ctx = (request.context && typeof request.context === 'object') ? request.context : {};
|
|
369
371
|
const ev = ctx.evidence_provided;
|
|
372
|
+
// §10.2 action_binding: the concrete call this descriptor claims to describe.
|
|
373
|
+
// Optional — an unbound request resolves exactly as before — but an authored
|
|
374
|
+
// authorization may refuse to RELAX anything without it (§10.5 require_binding).
|
|
375
|
+
const binding = normalizeActionBinding(request.action_binding);
|
|
370
376
|
return {
|
|
371
377
|
type,
|
|
372
|
-
|
|
378
|
+
// NORMALIZED only (case/whitespace) — deliberately NOT vocabulary-mapped. The
|
|
379
|
+
// request domain feeds §10.5 matching, and a relaxing rule must keep matching on
|
|
380
|
+
// the literal the agent sent; `domainCovers` applies the vocabulary itself, and
|
|
381
|
+
// only for restrictive rules. Mapping here instead would let an alias widen a
|
|
382
|
+
// grant. The RECEIPT stores the canonical form (that is the evidence axis).
|
|
383
|
+
domain: typeof ad.domain === 'string' ? (normalizeDomain(ad.domain) || undefined) : undefined,
|
|
373
384
|
reversible: typeof ad.reversible === 'boolean' ? ad.reversible : undefined,
|
|
374
|
-
|
|
385
|
+
// UNSTATED STAYS UNSTATED (v0.1.68). This defaulted to 'medium', which meant the resolver could
|
|
386
|
+
// never tell "the agent said medium" from "the agent said nothing" — and §10.5 then let a
|
|
387
|
+
// medium-capped grant fire on the nothing. The receipt now records what was actually sent
|
|
388
|
+
// (null), which is also what situational precedent (OQ24) needs to band on. The inferred
|
|
389
|
+
// oracle path still reads an unknown as the middle of the scale (`magnitudeRank`); that is a
|
|
390
|
+
// disposition heuristic, not a grant, and it is unchanged.
|
|
391
|
+
magnitude,
|
|
375
392
|
amount: typeof ad.amount === 'number' && isFinite(ad.amount) ? ad.amount : undefined,
|
|
376
393
|
summary: typeof ad.summary === 'string' ? ad.summary : undefined,
|
|
377
394
|
evidenceProvided: Array.isArray(ev) ? ev : (ev != null ? [ev] : []),
|
|
395
|
+
binding,
|
|
396
|
+
bound: isBound(binding),
|
|
378
397
|
};
|
|
379
398
|
}
|
|
380
399
|
|
|
@@ -500,22 +519,50 @@ export function consultDrivers(vd, request) {
|
|
|
500
519
|
// action's domain when it had one, so a financial override never reshapes writing.
|
|
501
520
|
const PERMISSIVE_SIGNAL = new Set(['proceed', 'proceed_with_note']);
|
|
502
521
|
const CAUTIOUS_SIGNAL = new Set(['escalate', 'decline', 'clarify']);
|
|
503
|
-
|
|
522
|
+
|
|
523
|
+
// A delegated correction's signed weight — direction read from the subject's action
|
|
524
|
+
// RELATIVE to the oracle's signal, NOT the signal class alone. This is the fix for a
|
|
525
|
+
// direction inversion: a lesson requires DISAGREEMENT between signal and action.
|
|
526
|
+
// permissive + rejected → +1 said go, subject didn't proceed → wants CAUTION
|
|
527
|
+
// cautious + approved → -1 said hold, subject proceeded → wants BOLD
|
|
528
|
+
// modified → ±0.5 (ambiguous change; signal-relative, half weight)
|
|
529
|
+
// permissive + approved → 0 AGREEMENT (both proceed) — teaches nothing
|
|
530
|
+
// cautious + rejected → 0 AGREEMENT (both hold) — teaches nothing
|
|
531
|
+
// This keeps the anti-obedience rail intact (a rubber-stamp `approved` on a `proceed`
|
|
532
|
+
// signal is agreement, not a "be bolder" lesson) while no longer misreading the
|
|
533
|
+
// subject AGREEING with a cautious signal as a demand to be bolder — which was
|
|
534
|
+
// eroding the very guardrails the subject was upholding (agreement.js counts
|
|
535
|
+
// decline+rejected as agreement; this now matches).
|
|
536
|
+
function delegationCorrection(signal, userAction) {
|
|
537
|
+
const permissive = PERMISSIVE_SIGNAL.has(signal);
|
|
538
|
+
const cautious = CAUTIOUS_SIGNAL.has(signal);
|
|
539
|
+
if (!permissive && !cautious) return 0; // abstain / unknown signal
|
|
540
|
+
if (userAction === 'modified') return permissive ? 0.5 : -0.5;
|
|
541
|
+
if (permissive && userAction === 'rejected') return 1; // override toward caution
|
|
542
|
+
if (cautious && userAction === 'approved') return -1; // override toward bold (proceeded anyway)
|
|
543
|
+
return 0; // agreement / unknown → no lesson
|
|
544
|
+
}
|
|
504
545
|
|
|
505
546
|
export function deriveDelegationSignals(receipts, { now = new Date().toISOString(), floor = 1 } = {}) {
|
|
506
547
|
const tally = new Map(); // `${cue}|${domain}` → { cue, domain, sum, n }
|
|
507
548
|
for (const r of (Array.isArray(receipts) ? receipts : [])) {
|
|
508
|
-
const w = OVERRIDE_WEIGHT[r && r.user_action];
|
|
509
|
-
if (!w) continue; // only rejected/modified teach
|
|
510
549
|
const sig = r?.response?.signal;
|
|
511
|
-
const
|
|
512
|
-
if (!
|
|
513
|
-
|
|
550
|
+
const contribution = delegationCorrection(sig, r && r.user_action); // signed; 0 = agreement/no lesson
|
|
551
|
+
if (!contribution) continue;
|
|
552
|
+
// Canonicalize the domain BEFORE bucketing, or one concept spelled several ways
|
|
553
|
+
// (finance / financial / Finance) fragments into several near-identical buckets — the
|
|
554
|
+
// drift §10.2's published vocabulary exists to end. Both the live overlay
|
|
555
|
+
// (deriveDelegationSignals) and the promotion cards (buildDelegationPromotions) key on
|
|
556
|
+
// this, so both must canonicalize or a card floods / an overlay splits. Free text still
|
|
557
|
+
// survives — an unrecognized domain canonicalizes to itself — so only known synonyms
|
|
558
|
+
// merge, never distinct classes.
|
|
559
|
+
const domain = canonicalDomain(
|
|
560
|
+
(typeof r?.request?.action_descriptor?.domain === 'string') ? r.request.action_descriptor.domain : '');
|
|
514
561
|
for (const d of (Array.isArray(r.drivers) ? r.drivers : [])) {
|
|
515
562
|
if (!CUES[d && d.cue]) continue; // scalar caution cues only (ambiguity later)
|
|
516
563
|
const k = `${d.cue}|${domain}`;
|
|
517
564
|
const t = tally.get(k) || { cue: d.cue, domain, sum: 0, n: 0 };
|
|
518
|
-
t.sum +=
|
|
565
|
+
t.sum += contribution; t.n += 1;
|
|
519
566
|
tally.set(k, t);
|
|
520
567
|
}
|
|
521
568
|
}
|
|
@@ -600,22 +647,28 @@ function delegationText(dimension, dir, domain) {
|
|
|
600
647
|
|
|
601
648
|
export function buildDelegationPromotions(receipts, fp, { now = new Date().toISOString(), floor = DELEGATION_PROMOTION_FLOOR } = {}) {
|
|
602
649
|
// Aggregate overrides per CANONICAL (dimension, domain): signed weight + count. Same
|
|
603
|
-
//
|
|
604
|
-
//
|
|
650
|
+
// signal-relative direction rule as deriveDelegationSignals — a lesson requires the
|
|
651
|
+
// subject's action to DISAGREE with the oracle's signal; agreement teaches nothing.
|
|
605
652
|
const tally = new Map(); // `${dimension}|${domain}` → { dimension, domain, sum, n }
|
|
606
653
|
for (const r of (Array.isArray(receipts) ? receipts : [])) {
|
|
607
|
-
const w = OVERRIDE_WEIGHT[r && r.user_action];
|
|
608
|
-
if (!w) continue;
|
|
609
654
|
const sig = r?.response?.signal;
|
|
610
|
-
const
|
|
611
|
-
if (!
|
|
612
|
-
|
|
655
|
+
const contribution = delegationCorrection(sig, r && r.user_action);
|
|
656
|
+
if (!contribution) continue;
|
|
657
|
+
// Canonicalize the domain BEFORE bucketing, or one concept spelled several ways
|
|
658
|
+
// (finance / financial / Finance) fragments into several near-identical buckets — the
|
|
659
|
+
// drift §10.2's published vocabulary exists to end. Both the live overlay
|
|
660
|
+
// (deriveDelegationSignals) and the promotion cards (buildDelegationPromotions) key on
|
|
661
|
+
// this, so both must canonicalize or a card floods / an overlay splits. Free text still
|
|
662
|
+
// survives — an unrecognized domain canonicalizes to itself — so only known synonyms
|
|
663
|
+
// merge, never distinct classes.
|
|
664
|
+
const domain = canonicalDomain(
|
|
665
|
+
(typeof r?.request?.action_descriptor?.domain === 'string') ? r.request.action_descriptor.domain : '');
|
|
613
666
|
for (const d of (Array.isArray(r.drivers) ? r.drivers : [])) {
|
|
614
667
|
const dim = CUE_DIMENSION[d && d.cue];
|
|
615
668
|
if (!dim) continue;
|
|
616
669
|
const k = `${dim}|${domain}`;
|
|
617
670
|
const t = tally.get(k) || { dimension: dim, domain, sum: 0, n: 0 };
|
|
618
|
-
t.sum +=
|
|
671
|
+
t.sum += contribution; t.n += 1; tally.set(k, t);
|
|
619
672
|
}
|
|
620
673
|
}
|
|
621
674
|
const keyOf = (dim, domain) => `${dim}|${domain || ''}`;
|
|
@@ -726,6 +779,15 @@ export function ruleResponse(vd, n) {
|
|
|
726
779
|
return shape(n.type, resolved.signal, cal.confidence, resolved.hint, cal.deference_level);
|
|
727
780
|
}
|
|
728
781
|
|
|
782
|
+
// The §10.5 evaluation context, lifted out of `opts` once so both public entries
|
|
783
|
+
// gate identically. `clientId` is the SERVING paired client's own `client_id` (from
|
|
784
|
+
// the server's `pairing.json`, supplied by the transport layer) — the identity
|
|
785
|
+
// `agents` scoping matches on; never anything the caller asserted. Absent here means
|
|
786
|
+
// unpaired, which makes every `agents`-scoped relaxing rule inert. Passing `now`
|
|
787
|
+
// through as well keeps rule expiry on the same clock `loadPolicies` already gives
|
|
788
|
+
// §5.7 commitments; both fall back to `Date.now()` when a caller omits them.
|
|
789
|
+
const policyCtx = (opts = {}) => ({ now: opts.now, clientId: opts.clientId ?? null });
|
|
790
|
+
|
|
729
791
|
// ── Public pure entry ─────────────────────────────────────────────────────────
|
|
730
792
|
// (vd, request[, opts]) -> §10.3 response. Deterministic; applies authored §10.5
|
|
731
793
|
// policies last so a standing guardrail wins (even over an abstain). `opts.policies`
|
|
@@ -734,7 +796,7 @@ export function ruleResponse(vd, n) {
|
|
|
734
796
|
export function resolveConsultationCore(vd, request = {}, opts = {}) {
|
|
735
797
|
assertVaultUsable(vd);
|
|
736
798
|
const n = normalizeRequest(request);
|
|
737
|
-
return applyPolicies(ruleResponse(vd, n), n, loadPolicies(vd, opts));
|
|
799
|
+
return applyPolicies(ruleResponse(vd, n), n, loadPolicies(vd, opts), policyCtx(opts));
|
|
738
800
|
}
|
|
739
801
|
|
|
740
802
|
// Back-compat alias: the prior public name of the sync rule entry. Kept so existing
|
|
@@ -751,7 +813,7 @@ export const buildConsultationResponse = resolveConsultationCore;
|
|
|
751
813
|
export function checkAuthorization(vd, request = {}, opts = {}) {
|
|
752
814
|
assertVaultUsable(vd);
|
|
753
815
|
const n = normalizeRequest(request);
|
|
754
|
-
const auth = matchAuthorization(n, loadPolicies(vd, opts));
|
|
816
|
+
const auth = matchAuthorization(n, loadPolicies(vd, opts), policyCtx(opts));
|
|
755
817
|
return {
|
|
756
818
|
phaedo_response_version: '0.1',
|
|
757
819
|
request_type: 'authorization_check',
|