instar 1.3.349 → 1.3.350
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/core/PrincipalGuard.d.ts +64 -0
- package/dist/core/PrincipalGuard.d.ts.map +1 -0
- package/dist/core/PrincipalGuard.js +125 -0
- package/dist/core/PrincipalGuard.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/{1.3.349.md → 1.3.350.md} +21 -0
- package/upgrades/side-effects/principal-guard-phase1.md +39 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrincipalGuard — cross-principal attribution detector (EXO 3.0 / "Know Your
|
|
3
|
+
* Principal" standard, Phase 1 brain).
|
|
4
|
+
*
|
|
5
|
+
* The "Caroline" incident (2026-06-05, topic 19437): an autonomous session
|
|
6
|
+
* silently adopted a real OTHER principal as its operator and credited the
|
|
7
|
+
* actual operator's decisions to her across three docs — and no gate watched,
|
|
8
|
+
* because the contamination was in the agent's OWN output, not an inbound
|
|
9
|
+
* message. This module is the pure-logic detector for that failure: given a
|
|
10
|
+
* piece of agent-authored text, it finds operator-ROLE-decision attributions
|
|
11
|
+
* ("X approved", "locked with X", "mandate (X)", "X dropped a token") and
|
|
12
|
+
* flags any whose attributed principal is NOT the topic's bound operator and
|
|
13
|
+
* does NOT resolve to a known user.
|
|
14
|
+
*
|
|
15
|
+
* Pure + deterministic so it is unit-testable in isolation; it performs no I/O
|
|
16
|
+
* and is not wired into the live request path here (that is a later increment).
|
|
17
|
+
* The constitution standard it enforces: "Know Your Principal — An Unverified
|
|
18
|
+
* Identity Is a Guess" (docs/STANDARDS-REGISTRY.md). Spec:
|
|
19
|
+
* docs/specs/OPERATOR-IDENTITY-BINDING-SPEC.md.
|
|
20
|
+
*/
|
|
21
|
+
/** A verified operator, established ONLY from an authenticated channel — never
|
|
22
|
+
* from a name read in content (that is the whole point). */
|
|
23
|
+
export interface VerifiedOperator {
|
|
24
|
+
/** The platform-verified sender id (e.g. Telegram uid). */
|
|
25
|
+
uid: string;
|
|
26
|
+
/** Display name(s) the operator is known by, lowercased for matching. */
|
|
27
|
+
names: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Establish a topic's operator from the AUTHENTICATED sender — never from a
|
|
31
|
+
* content name. The uid is the authority; names are only for matching the
|
|
32
|
+
* agent's prose against the verified principal. A blank uid yields no operator
|
|
33
|
+
* (an unbound topic), which the guard treats as "everything is unverified".
|
|
34
|
+
*/
|
|
35
|
+
export declare function establishOperator(authenticatedUid: string, displayName?: string): VerifiedOperator | null;
|
|
36
|
+
/** The kind of operator-role decision an attribution carries — drives warn vs block. */
|
|
37
|
+
export type AttributionKind = 'approval' | 'mandate' | 'credential' | 'lock' | 'acting-for';
|
|
38
|
+
export interface Attribution {
|
|
39
|
+
/** The principal name the decision was attributed to (lowercased). */
|
|
40
|
+
principal: string;
|
|
41
|
+
kind: AttributionKind;
|
|
42
|
+
/** The matched snippet, for the audit trail. */
|
|
43
|
+
snippet: string;
|
|
44
|
+
}
|
|
45
|
+
/** Find every operator-role-decision attribution in agent-authored text. */
|
|
46
|
+
export declare function detectAttributions(text: string): Attribution[];
|
|
47
|
+
export type Verdict = 'ok' | 'warn' | 'block';
|
|
48
|
+
export interface PrincipalFinding {
|
|
49
|
+
attribution: Attribution;
|
|
50
|
+
verdict: Verdict;
|
|
51
|
+
reason: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Evaluate a piece of agent-authored text against the topic's verified
|
|
55
|
+
* operator and the known-user registry. An attribution to the bound operator
|
|
56
|
+
* (or any known user) is fine; an attribution to anyone else is the Caroline
|
|
57
|
+
* failure — warn for prose, block for authority/credential decisions.
|
|
58
|
+
*
|
|
59
|
+
* `knownUserNames` are lowercased display names from UserManager (the
|
|
60
|
+
* authoritative registry). Resolution against it is what the "Know Your
|
|
61
|
+
* Principal" standard requires before any principal is accepted.
|
|
62
|
+
*/
|
|
63
|
+
export declare function evaluatePrincipalCoherence(text: string, operator: VerifiedOperator | null, knownUserNames?: string[]): PrincipalFinding[];
|
|
64
|
+
//# sourceMappingURL=PrincipalGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrincipalGuard.d.ts","sourceRoot":"","sources":["../../src/core/PrincipalGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;6DAC6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAKzG;AAED,wFAAwF;AACxF,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,SAAS,GACT,YAAY,GACZ,MAAM,GACN,YAAY,CAAC;AAEjB,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,eAAe,CAAC;IACtB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AA0CD,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAgB9D;AAED,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,cAAc,GAAE,MAAM,EAAO,GAC5B,gBAAgB,EAAE,CAqBpB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrincipalGuard — cross-principal attribution detector (EXO 3.0 / "Know Your
|
|
3
|
+
* Principal" standard, Phase 1 brain).
|
|
4
|
+
*
|
|
5
|
+
* The "Caroline" incident (2026-06-05, topic 19437): an autonomous session
|
|
6
|
+
* silently adopted a real OTHER principal as its operator and credited the
|
|
7
|
+
* actual operator's decisions to her across three docs — and no gate watched,
|
|
8
|
+
* because the contamination was in the agent's OWN output, not an inbound
|
|
9
|
+
* message. This module is the pure-logic detector for that failure: given a
|
|
10
|
+
* piece of agent-authored text, it finds operator-ROLE-decision attributions
|
|
11
|
+
* ("X approved", "locked with X", "mandate (X)", "X dropped a token") and
|
|
12
|
+
* flags any whose attributed principal is NOT the topic's bound operator and
|
|
13
|
+
* does NOT resolve to a known user.
|
|
14
|
+
*
|
|
15
|
+
* Pure + deterministic so it is unit-testable in isolation; it performs no I/O
|
|
16
|
+
* and is not wired into the live request path here (that is a later increment).
|
|
17
|
+
* The constitution standard it enforces: "Know Your Principal — An Unverified
|
|
18
|
+
* Identity Is a Guess" (docs/STANDARDS-REGISTRY.md). Spec:
|
|
19
|
+
* docs/specs/OPERATOR-IDENTITY-BINDING-SPEC.md.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Establish a topic's operator from the AUTHENTICATED sender — never from a
|
|
23
|
+
* content name. The uid is the authority; names are only for matching the
|
|
24
|
+
* agent's prose against the verified principal. A blank uid yields no operator
|
|
25
|
+
* (an unbound topic), which the guard treats as "everything is unverified".
|
|
26
|
+
*/
|
|
27
|
+
export function establishOperator(authenticatedUid, displayName) {
|
|
28
|
+
const uid = String(authenticatedUid ?? '').trim();
|
|
29
|
+
if (!uid)
|
|
30
|
+
return null;
|
|
31
|
+
const names = (displayName ?? '').trim() ? [displayName.trim().toLowerCase()] : [];
|
|
32
|
+
return { uid, names };
|
|
33
|
+
}
|
|
34
|
+
// ── Detection patterns ───────────────────────────────────────────────
|
|
35
|
+
// Each captures a person-like principal NAME in group 1. Kept conservative:
|
|
36
|
+
// a name is 1-3 capitalized-ish words (so "the team" / "prod" don't match).
|
|
37
|
+
// NAME requires a real capital first letter (case-SENSITIVE — no `i` flag — so
|
|
38
|
+
// "have"/"production" can never be captured as a principal). Only the keyword
|
|
39
|
+
// LEADS are made case-flexible ([Mm]andate, [Ll]ock…) so a sentence-initial
|
|
40
|
+
// "Mandate" matches as well as a mid-sentence "mandate".
|
|
41
|
+
const NAME = String.raw `([A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+){0,2})`;
|
|
42
|
+
// Bounded gap that lets a date/short clause sit between a keyword and the name
|
|
43
|
+
// ("Locked 2026-06-04 with Caroline") without swallowing a sentence.
|
|
44
|
+
const GAP = String.raw `[^.\n]{0,40}?`;
|
|
45
|
+
const PATTERNS = [
|
|
46
|
+
{ re: new RegExp(String.raw `\b[Mm]andate\s*\(\s*${NAME}\s*\)`, 'g'), kind: 'mandate' },
|
|
47
|
+
{ re: new RegExp(String.raw `\b${NAME}\s+[Aa]uthoriz(?:ed|es)\b`, 'g'), kind: 'mandate' },
|
|
48
|
+
{ re: new RegExp(String.raw `\b${NAME}\s+[Aa]pprov(?:ed|es)\b`, 'g'), kind: 'approval' },
|
|
49
|
+
{ re: new RegExp(String.raw `\b(?:[Bb]lessed|[Ss]igned[- ]off)\s+by\s+${NAME}`, 'g'), kind: 'approval' },
|
|
50
|
+
{ re: new RegExp(String.raw `\b[Ll]ock(?:ed)?\b${GAP}\b(?:with|by)\s+${NAME}`, 'g'), kind: 'lock' },
|
|
51
|
+
{ re: new RegExp(String.raw `\b[Ss]tanding requirement from\s+${NAME}`, 'g'), kind: 'lock' },
|
|
52
|
+
{ re: new RegExp(String.raw `\b${NAME}\s+(?:dropped|drop)\s+(?:a|the)\s+(?:token|credential|secret|key)`, 'g'), kind: 'credential' },
|
|
53
|
+
{ re: new RegExp(String.raw `\b${NAME}['’]s\s+(?:credential|token|git\s+cred)`, 'g'), kind: 'credential' },
|
|
54
|
+
{ re: new RegExp(String.raw `\b[Oo]n\s+behalf\s+of\s+${NAME}`, 'g'), kind: 'acting-for' },
|
|
55
|
+
{ re: new RegExp(String.raw `\b[Oo]n\s+${NAME}['’]s\s+(?:say-so|behalf|authority)`, 'g'), kind: 'acting-for' },
|
|
56
|
+
];
|
|
57
|
+
// Words that look capitalized but are never principals (sentence-start nouns,
|
|
58
|
+
// product/role terms). Keeps the detector from flagging "Production approved".
|
|
59
|
+
const NON_PRINCIPALS = new Set([
|
|
60
|
+
'the', 'a', 'an', 'prod', 'production', 'ci', 'team', 'board', 'ops', 'staging',
|
|
61
|
+
'github', 'telegram', 'slack', 'dawn', 'echo', 'i', 'we', 'you', 'they', 'it',
|
|
62
|
+
'have', 'has', 'this', 'that', 'every', 'all',
|
|
63
|
+
]);
|
|
64
|
+
function isPersonLike(name) {
|
|
65
|
+
const firstWord = name.split(/\s+/)[0];
|
|
66
|
+
// The `i` flag lets [A-Z] match lowercase too, so require an actual capital
|
|
67
|
+
// first letter here — a real person name is capitalized; "production" is not.
|
|
68
|
+
const startsCapital = /^[A-Z]/.test(firstWord);
|
|
69
|
+
return startsCapital && !NON_PRINCIPALS.has(firstWord.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/** Find every operator-role-decision attribution in agent-authored text. */
|
|
72
|
+
export function detectAttributions(text) {
|
|
73
|
+
const out = [];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const { re, kind } of PATTERNS) {
|
|
76
|
+
re.lastIndex = 0;
|
|
77
|
+
let m;
|
|
78
|
+
while ((m = re.exec(text)) !== null) {
|
|
79
|
+
const principal = (m[1] ?? '').trim();
|
|
80
|
+
if (!principal || !isPersonLike(principal))
|
|
81
|
+
continue;
|
|
82
|
+
const key = `${kind}:${principal.toLowerCase()}:${m.index}`;
|
|
83
|
+
if (seen.has(key))
|
|
84
|
+
continue;
|
|
85
|
+
seen.add(key);
|
|
86
|
+
out.push({ principal: principal.toLowerCase(), kind, snippet: m[0] });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/** Credential/authority-bearing kinds BLOCK when misattributed; the rest WARN. */
|
|
92
|
+
const BLOCK_KINDS = new Set(['mandate', 'credential']);
|
|
93
|
+
/**
|
|
94
|
+
* Evaluate a piece of agent-authored text against the topic's verified
|
|
95
|
+
* operator and the known-user registry. An attribution to the bound operator
|
|
96
|
+
* (or any known user) is fine; an attribution to anyone else is the Caroline
|
|
97
|
+
* failure — warn for prose, block for authority/credential decisions.
|
|
98
|
+
*
|
|
99
|
+
* `knownUserNames` are lowercased display names from UserManager (the
|
|
100
|
+
* authoritative registry). Resolution against it is what the "Know Your
|
|
101
|
+
* Principal" standard requires before any principal is accepted.
|
|
102
|
+
*/
|
|
103
|
+
export function evaluatePrincipalCoherence(text, operator, knownUserNames = []) {
|
|
104
|
+
const known = new Set([
|
|
105
|
+
...(operator?.names ?? []),
|
|
106
|
+
...knownUserNames.map((n) => n.trim().toLowerCase()).filter(Boolean),
|
|
107
|
+
]);
|
|
108
|
+
const findings = [];
|
|
109
|
+
for (const attribution of detectAttributions(text)) {
|
|
110
|
+
if (known.has(attribution.principal))
|
|
111
|
+
continue; // resolves to a verified principal — fine
|
|
112
|
+
const verdict = BLOCK_KINDS.has(attribution.kind) ? 'block' : 'warn';
|
|
113
|
+
findings.push({
|
|
114
|
+
attribution,
|
|
115
|
+
verdict,
|
|
116
|
+
reason: `Operator-role ${attribution.kind} attributed to "${attribution.principal}", who is ` +
|
|
117
|
+
(operator
|
|
118
|
+
? `not this topic's bound operator and does not resolve to a known user`
|
|
119
|
+
: `unverifiable — this topic has no bound operator`) +
|
|
120
|
+
`. Know Your Principal: an unrecognized party in a decision role is a question to resolve, not a fact to accept.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return findings;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=PrincipalGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrincipalGuard.js","sourceRoot":"","sources":["../../src/core/PrincipalGuard.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-06T06:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-06T06:52:19.246Z",
|
|
5
|
+
"instarVersion": "1.3.350",
|
|
6
6
|
"entryCount": 199,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
## What Changed
|
|
7
7
|
|
|
8
|
+
Added `src/core/PrincipalGuard.ts` — the pure-logic brain of the "Know Your
|
|
9
|
+
Principal" cross-principal coherence guard (constitution standard ratified in
|
|
10
|
+
#898). It establishes a topic's operator only from the authenticated sender,
|
|
11
|
+
detects operator-role-decision attributions in agent-authored text ("X
|
|
12
|
+
approved", "Mandate (X)", "locked with X", "X dropped a token"), and flags any
|
|
13
|
+
credited to a principal who is neither the bound operator nor a known user —
|
|
14
|
+
blocking authority/credential misattributions, warning on prose. No runtime
|
|
15
|
+
consumers yet (the wiring is a later increment).
|
|
16
|
+
|
|
8
17
|
Fixes #866: `POST /projects/:id/advance` building→merged always failed
|
|
9
18
|
`GH_PR_VIEW_UNAVAILABLE` because `StageTransitionValidator` has no internal
|
|
10
19
|
default for `ghPrView`/`gitMergeBaseIsAncestor` and the route never injected
|
|
@@ -16,6 +25,10 @@ error can never recur.
|
|
|
16
25
|
|
|
17
26
|
## What to Tell Your User
|
|
18
27
|
|
|
28
|
+
Nothing user-facing changes. This is foundation code (experimental) for the
|
|
29
|
+
security fix behind the Caroline identity-bleed incident — it does not alter any
|
|
30
|
+
current behavior.
|
|
31
|
+
|
|
19
32
|
If you drive a multi-spec initiative through the projects pipeline, its
|
|
20
33
|
items can now actually reach the "merged" stage — the system verifies the
|
|
21
34
|
work's pull request really merged (merge state, commit reachable on the
|
|
@@ -24,6 +37,9 @@ finally tracks reality through the whole pipeline.
|
|
|
24
37
|
|
|
25
38
|
## Summary of New Capabilities
|
|
26
39
|
|
|
40
|
+
- `PrincipalGuard` (experimental, no runtime consumers yet): `establishOperator`,
|
|
41
|
+
`detectAttributions`, `evaluatePrincipalCoherence`.
|
|
42
|
+
|
|
27
43
|
- `POST /projects/:id/advance` with `targetStage: "merged"` now works end to
|
|
28
44
|
end (was structurally impossible): it verifies the PR via `gh pr view` and
|
|
29
45
|
confirms the merge commit is reachable on `origin/main` before recording
|
|
@@ -31,6 +47,11 @@ finally tracks reality through the whole pipeline.
|
|
|
31
47
|
|
|
32
48
|
## Evidence
|
|
33
49
|
|
|
50
|
+
Net-new capability. Verified by 13 unit tests including the incident-replay
|
|
51
|
+
regression test (the three real Caroline doc lines all caught with the topic
|
|
52
|
+
bound to the operator; the same lines crediting the operator all pass) and a
|
|
53
|
+
clean `tsc --noEmit`.
|
|
54
|
+
|
|
34
55
|
- tests/integration/projects-api.test.ts: building→merged now returns
|
|
35
56
|
GH_PR_VIEW_FAILED (not UNAVAILABLE), deterministic regardless of whether
|
|
36
57
|
gh is installed/authed. Full projects-api (45) + StageTransitionValidator
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Side-effects review — PrincipalGuard (Phase 1 brain)
|
|
2
|
+
|
|
3
|
+
## What this change is
|
|
4
|
+
A new pure-logic module `src/core/PrincipalGuard.ts` (operator establishment +
|
|
5
|
+
cross-principal attribution detector + warn/block evaluator) and its unit test.
|
|
6
|
+
Implements the detector half of the just-ratified "Know Your Principal" standard
|
|
7
|
+
(the cross-principal coherence guard from OPERATOR-IDENTITY-BINDING-SPEC.md §2).
|
|
8
|
+
|
|
9
|
+
## Blast radius
|
|
10
|
+
- **Runtime impact: none.** Nothing imports `PrincipalGuard` at boot or in any
|
|
11
|
+
route/job/sentinel yet. Adding the file cannot change live behavior. It is the
|
|
12
|
+
testable brain; wiring it into the topic-operator binding, session-start
|
|
13
|
+
injection, and the outbound/at-rest review path are explicit later increments.
|
|
14
|
+
- **No I/O, no network, no new config/route/migration surface.** Pure functions
|
|
15
|
+
over strings + a `VerifiedOperator` value object.
|
|
16
|
+
- **Establishment is uid-only by construction:** `establishOperator` takes the
|
|
17
|
+
authenticated sender uid; there is NO code path that accepts a name from
|
|
18
|
+
content as the operator (the Caroline failure mode is impossible by type).
|
|
19
|
+
|
|
20
|
+
## Security review
|
|
21
|
+
- The detector is conservative: a name must be capitalized (case-sensitive,
|
|
22
|
+
not the `i` flag) and not in a non-principal stop-set, so "Production
|
|
23
|
+
approved" / "The Board approved" do not flag. False positives warn (prose) or
|
|
24
|
+
block (authority/credential) — they never silently pass a misattribution.
|
|
25
|
+
- No payload/secret handling; operates only on already-authored text.
|
|
26
|
+
|
|
27
|
+
## Framework generality
|
|
28
|
+
Pure logic, no session-launch/inject/message-delivery surface — framework-agnostic.
|
|
29
|
+
|
|
30
|
+
## Test coverage
|
|
31
|
+
13 unit tests covering both sides of every boundary: uid-only establishment
|
|
32
|
+
(incl. blank → null), each detection shape, non-principal rejection,
|
|
33
|
+
bound-operator pass, known-user pass, unknown-principal flag with block-vs-warn
|
|
34
|
+
tiering, no-operator case, and the **incident-replay regression test** — the
|
|
35
|
+
three real Caroline doc lines all caught (block for mandate/credential), and the
|
|
36
|
+
same lines crediting the bound operator all pass. `tsc --noEmit` clean.
|
|
37
|
+
|
|
38
|
+
## Rollback
|
|
39
|
+
Delete the module + test; zero runtime consequence (no consumers).
|