@stigmer/server 3.39.0 → 3.40.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/dist/artifactstorage/artifact-storage.d.ts +10 -2
- package/dist/artifactstorage/artifact-storage.d.ts.map +1 -1
- package/dist/artifactstorage/artifact-storage.js +6 -3
- package/dist/artifactstorage/artifact-storage.js.map +1 -1
- package/dist/boot/artifact-lane.d.ts +55 -0
- package/dist/boot/artifact-lane.d.ts.map +1 -0
- package/dist/boot/artifact-lane.js +74 -0
- package/dist/boot/artifact-lane.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +42 -19
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/config.d.ts +11 -6
- package/dist/boot/config.d.ts.map +1 -1
- package/dist/boot/config.js +10 -5
- package/dist/boot/config.js.map +1 -1
- package/dist/domain/agent/controller.d.ts +5 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts +18 -4
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +2 -2
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +29 -15
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +118 -51
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +8 -2
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +3 -1
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +12 -6
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts +9 -4
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +3 -3
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/artifact/file-server.d.ts +2 -1
- package/dist/domain/artifact/file-server.d.ts.map +1 -1
- package/dist/domain/artifact/file-server.js +6 -3
- package/dist/domain/artifact/file-server.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts +8 -3
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts +8 -5
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +3 -3
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/executioncontext/controller.d.ts +7 -4
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +6 -2
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/memory/constants.d.ts +9 -0
- package/dist/domain/memory/constants.d.ts.map +1 -1
- package/dist/domain/memory/constants.js +9 -0
- package/dist/domain/memory/constants.js.map +1 -1
- package/dist/domain/memory/controller.d.ts +18 -6
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +18 -21
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +38 -10
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +87 -17
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/organization/controller.d.ts +7 -4
- package/dist/domain/organization/controller.d.ts.map +1 -1
- package/dist/domain/organization/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +10 -5
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/session/controller.d.ts +7 -3
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +15 -5
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts +6 -2
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +6 -3
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts +8 -4
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/update-status.d.ts.map +1 -1
- package/dist/domain/workflowexecution/update-status.js +3 -2
- package/dist/domain/workflowexecution/update-status.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts +2 -2
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +10 -5
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +5 -3
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +14 -9
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/identity.d.ts +28 -14
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +38 -0
- package/dist/extensions/identity.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +1 -13
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/package.json +6 -6
- package/src/artifactstorage/__tests__/artifact-storage.test.ts +15 -1
- package/src/artifactstorage/artifact-storage.ts +12 -4
- package/src/boot/__tests__/artifact-lane.test.ts +71 -0
- package/src/boot/__tests__/compose.test.ts +73 -3
- package/src/boot/__tests__/config.test.ts +19 -0
- package/src/boot/artifact-lane.ts +96 -0
- package/src/boot/compose.ts +57 -21
- package/src/boot/config.ts +25 -15
- package/src/domain/agent/controller.ts +5 -2
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +230 -2
- package/src/domain/agentexecution/controller.ts +32 -6
- package/src/domain/agentexecution/create-steps.ts +162 -62
- package/src/domain/agentinstance/__tests__/store-faults.test.ts +69 -18
- package/src/domain/agentinstance/controller.ts +8 -2
- package/src/domain/agentinstance/steps.ts +12 -6
- package/src/domain/agentshare/controller.ts +12 -7
- package/src/domain/artifact/file-server.ts +8 -4
- package/src/domain/channelapp/controller.ts +8 -3
- package/src/domain/environment/controller.ts +11 -8
- package/src/domain/executioncontext/controller.ts +7 -4
- package/src/domain/mcpserver/controller.ts +6 -2
- package/src/domain/memory/__tests__/capture-credential.test.ts +6 -6
- package/src/domain/memory/__tests__/person-consent-composed.test.ts +187 -0
- package/src/domain/memory/__tests__/person-consent.test.ts +274 -0
- package/src/domain/memory/constants.ts +11 -0
- package/src/domain/memory/controller.ts +36 -27
- package/src/domain/memory/steps.ts +112 -17
- package/src/domain/organization/controller.ts +7 -4
- package/src/domain/plugin/__tests__/plugin.test.ts +8 -0
- package/src/domain/plugin/__tests__/store-faults.test.ts +104 -0
- package/src/domain/plugin/controller.ts +10 -5
- package/src/domain/session/controller.ts +7 -3
- package/src/domain/skill/__tests__/skill.test.ts +4 -0
- package/src/domain/skill/__tests__/store-faults.test.ts +103 -0
- package/src/domain/skill/controller.ts +15 -5
- package/src/domain/workflow/__tests__/store-faults.test.ts +107 -0
- package/src/domain/workflow/controller.ts +12 -5
- package/src/domain/workflowexecution/controller.ts +8 -4
- package/src/domain/workflowexecution/update-status.ts +3 -2
- package/src/domain/workflowinstance/__tests__/store-faults.test.ts +172 -0
- package/src/domain/workflowinstance/controller.ts +12 -7
- package/src/domain/workflowinstance/steps.ts +14 -10
- package/src/extensions/__tests__/identity.test.ts +82 -0
- package/src/extensions/identity.ts +47 -0
- package/src/pipeline/__tests__/blind-not-found.test.ts +4 -20
- package/src/pipeline/steps/guard-memory-capture.ts +4 -19
|
@@ -34,6 +34,7 @@ import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_
|
|
|
34
34
|
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
35
35
|
import type { SessionSpec } from "@stigmer/protos/ai/stigmer/agentic/session/v1/spec_pb";
|
|
36
36
|
import { SessionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/spec_pb";
|
|
37
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
37
38
|
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
38
39
|
import { OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
39
40
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
@@ -41,6 +42,7 @@ import { clone } from "@bufbuild/protobuf";
|
|
|
41
42
|
|
|
42
43
|
import type { Logger } from "../../boot/logger.js";
|
|
43
44
|
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
45
|
+
import { isFirstPartyHumanOperator } from "../../extensions/identity.js";
|
|
44
46
|
import type { ResourceAuthorizationLifecycle } from "../../extensions/resource-authorization.js";
|
|
45
47
|
import { notifyDefaultInstanceLinked } from "../../pipeline/steps/authorization-tuples.js";
|
|
46
48
|
import {
|
|
@@ -53,9 +55,12 @@ import {
|
|
|
53
55
|
} from "../../pipeline/errors.js";
|
|
54
56
|
import { ConnectError } from "@connectrpc/connect";
|
|
55
57
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
58
|
+
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
56
59
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
57
60
|
import type { Store } from "../../store/interface.js";
|
|
58
61
|
import { buildDefaultInstanceRequest } from "../agentinstance/defaultinstance.js";
|
|
62
|
+
import type { AccountsByCaller } from "../identityaccount/resolve.js";
|
|
63
|
+
import { accountForCaller } from "../identityaccount/resolve.js";
|
|
59
64
|
|
|
60
65
|
import type { AgentExecutionStatusObserver } from "../../extensions/status-hooks.js";
|
|
61
66
|
|
|
@@ -416,19 +421,27 @@ export function newCreateSessionIfNeededStep(deps: {
|
|
|
416
421
|
// ---------------------------------------------------------------------------
|
|
417
422
|
|
|
418
423
|
/**
|
|
419
|
-
* Snapshots the
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* org_context
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
424
|
+
* Snapshots the declared standing context onto the execution spec
|
|
425
|
+
* (DD-002, stigmer/stigmer#293). SERVER-OWNED: stamped unconditionally,
|
|
426
|
+
* overwriting anything the caller supplied. Two independent halves:
|
|
427
|
+
* org_context, the organization's, for every run; and user_context, the
|
|
428
|
+
* run's person's own (stigmer#1397) — composed only where callers are
|
|
429
|
+
* persons (`personAccounts`, the require-authentication posture) and only
|
|
430
|
+
* for a first-party human operator (runPersonOf), as the Java step did.
|
|
431
|
+
* The single-operator posture composes org_context alone: its one
|
|
432
|
+
* operator's context is the organization's. The org half deliberately
|
|
433
|
+
* keeps running for every lane (schedule and channel runs carry the
|
|
434
|
+
* organization's instructions today); only the person's half is about a
|
|
435
|
+
* person. BEST-EFFORT: an execution must never fail to start because its
|
|
436
|
+
* optional preferences could not load — genuine failures log at ERROR
|
|
437
|
+
* (quiet degradation of a should-work path must stay visible) and degrade
|
|
438
|
+
* that half to empty. Snapshot-at-create is the point: preferences are
|
|
427
439
|
* mutable, executions are immutable audit records.
|
|
428
440
|
*/
|
|
429
441
|
export function newComposeDeclaredPreferencesStep(
|
|
430
442
|
store: Store,
|
|
431
443
|
logger: Logger,
|
|
444
|
+
personAccounts?: AccountsByCaller,
|
|
432
445
|
): PipelineStep<CreateDesc> {
|
|
433
446
|
return {
|
|
434
447
|
name: "ComposeDeclaredPreferences",
|
|
@@ -438,50 +451,103 @@ export function newComposeDeclaredPreferencesStep(
|
|
|
438
451
|
// Claim the server-owned field first, before any load can fail.
|
|
439
452
|
execution.spec.declaredPreferences = create(DeclaredPreferencesSchema);
|
|
440
453
|
|
|
441
|
-
let orgId = execution.metadata?.org ?? "";
|
|
442
|
-
if (orgId === "") {
|
|
443
|
-
orgId = ctx.input.metadata?.org ?? "";
|
|
444
|
-
}
|
|
445
|
-
if (orgId === "") {
|
|
446
|
-
logger.debug(
|
|
447
|
-
"No org on execution metadata, composing no declared preferences",
|
|
448
|
-
);
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
let org: Organization;
|
|
453
|
-
try {
|
|
454
|
-
org = await store.getResource(
|
|
455
|
-
ApiResourceKind.organization,
|
|
456
|
-
orgId,
|
|
457
|
-
OrganizationSchema,
|
|
458
|
-
);
|
|
459
|
-
} catch (error) {
|
|
460
|
-
if (error instanceof ResourceNotFoundError) {
|
|
461
|
-
logger.debug(
|
|
462
|
-
"Org not found in store, composing no declared preferences",
|
|
463
|
-
{ orgId },
|
|
464
|
-
);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
logger.error(
|
|
468
|
-
"Failed to load org for declared preferences - degrading to none (best-effort contract)",
|
|
469
|
-
{
|
|
470
|
-
orgId,
|
|
471
|
-
error: error instanceof Error ? error.message : String(error),
|
|
472
|
-
},
|
|
473
|
-
);
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
454
|
// Verbatim per DD-002 D2: the server stamps content only;
|
|
478
455
|
// blank-is-absent is the runner's read-side convention.
|
|
479
456
|
execution.spec.declaredPreferences.orgContext =
|
|
480
|
-
|
|
457
|
+
await loadOrgStandingContext(store, logger, orgIdOf(ctx));
|
|
458
|
+
|
|
459
|
+
if (personAccounts === undefined) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const person = await runPersonOf(
|
|
463
|
+
personAccounts,
|
|
464
|
+
ctx.callerIdentity,
|
|
465
|
+
logger,
|
|
466
|
+
"Failed to load the run's person for declared preferences - degrading user context to none (best-effort contract)",
|
|
467
|
+
);
|
|
468
|
+
execution.spec.declaredPreferences.userContext =
|
|
469
|
+
person?.spec?.preferences?.standingContext ?? "";
|
|
481
470
|
},
|
|
482
471
|
};
|
|
483
472
|
}
|
|
484
473
|
|
|
474
|
+
/** The org a create addresses: the execution's metadata, else the input's. */
|
|
475
|
+
function orgIdOf(ctx: RequestContext<CreateDesc>): string {
|
|
476
|
+
const orgId = ctx.newState.metadata?.org ?? "";
|
|
477
|
+
return orgId !== "" ? orgId : (ctx.input.metadata?.org ?? "");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** The organization's standing context, best-effort ("" on every degrade). */
|
|
481
|
+
async function loadOrgStandingContext(
|
|
482
|
+
store: Store,
|
|
483
|
+
logger: Logger,
|
|
484
|
+
orgId: string,
|
|
485
|
+
): Promise<string> {
|
|
486
|
+
if (orgId === "") {
|
|
487
|
+
logger.debug(
|
|
488
|
+
"No org on execution metadata, composing no declared preferences",
|
|
489
|
+
);
|
|
490
|
+
return "";
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
let org: Organization;
|
|
494
|
+
try {
|
|
495
|
+
org = await store.getResource(
|
|
496
|
+
ApiResourceKind.organization,
|
|
497
|
+
orgId,
|
|
498
|
+
OrganizationSchema,
|
|
499
|
+
);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
if (error instanceof ResourceNotFoundError) {
|
|
502
|
+
logger.debug(
|
|
503
|
+
"Org not found in store, composing no declared preferences",
|
|
504
|
+
{
|
|
505
|
+
orgId,
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
return "";
|
|
509
|
+
}
|
|
510
|
+
logger.error(
|
|
511
|
+
"Failed to load org for declared preferences - degrading to none (best-effort contract)",
|
|
512
|
+
{
|
|
513
|
+
orgId,
|
|
514
|
+
error: error instanceof Error ? error.message : String(error),
|
|
515
|
+
},
|
|
516
|
+
);
|
|
517
|
+
return "";
|
|
518
|
+
}
|
|
519
|
+
return org.spec?.preferences?.standingContext ?? "";
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* The person a run is created by, where callers are persons
|
|
524
|
+
* (stigmer#1387): the account a first-party human operator stands for
|
|
525
|
+
* (extensions/identity.ts states who that is, once). `undefined` for every
|
|
526
|
+
* other lane — runner, schedule, channel, guest, machine, a PlatformClient
|
|
527
|
+
* token, anything server-composed — for a caller no account stands for,
|
|
528
|
+
* and, best-effort, on a store fault (logged at ERROR with the caller's
|
|
529
|
+
* copy: the compose steps degrade, they never fail a create).
|
|
530
|
+
*/
|
|
531
|
+
async function runPersonOf(
|
|
532
|
+
personAccounts: AccountsByCaller,
|
|
533
|
+
caller: CallerIdentity,
|
|
534
|
+
logger: Logger,
|
|
535
|
+
faultMessage: string,
|
|
536
|
+
): Promise<IdentityAccount | undefined> {
|
|
537
|
+
if (!isFirstPartyHumanOperator(caller)) {
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
return await accountForCaller(personAccounts, caller);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
logger.error(faultMessage, {
|
|
544
|
+
identityId: caller.identityId,
|
|
545
|
+
error: error instanceof Error ? error.message : String(error),
|
|
546
|
+
});
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
485
551
|
// ---------------------------------------------------------------------------
|
|
486
552
|
// ComposeRecalledMemories — compose_recalled_memories_step.go.
|
|
487
553
|
// ---------------------------------------------------------------------------
|
|
@@ -492,18 +558,25 @@ export function newComposeDeclaredPreferencesStep(
|
|
|
492
558
|
* memory loop, sibling of ComposeDeclaredPreferences in every invariant:
|
|
493
559
|
* server-owned (enabled=false stamped on every ineligible/degraded path),
|
|
494
560
|
* best-effort (degrading to DISABLED, never enabled-with-zero-facts, so a
|
|
495
|
-
* broken recall never falsely offers the remember tool)
|
|
496
|
-
* org flag alone with the empty-string subject sentinel; confirmed-only
|
|
561
|
+
* broken recall never falsely offers the remember tool); confirmed-only
|
|
497
562
|
* (the consent gate is meaningless otherwise); no compose-time truncation
|
|
498
563
|
* (the runner's retriever selects at prompt build, recorded on
|
|
499
564
|
* status.recalled_memories_report). Facts order oldest-first on
|
|
500
|
-
* created_at — identical prompt order in
|
|
501
|
-
*
|
|
502
|
-
*
|
|
565
|
+
* created_at — identical prompt order in every posture.
|
|
566
|
+
*
|
|
567
|
+
* WHOSE memories follows the posture (stigmer#1387, the Java step's
|
|
568
|
+
* gates in its order): where callers are persons (`personAccounts`) the
|
|
569
|
+
* run must be created by a first-party human operator (runPersonOf), the
|
|
570
|
+
* organization's memory_enabled must be on, then that person's own — and
|
|
571
|
+
* the snapshot is that person's confirmed facts in the organization. The
|
|
572
|
+
* single-operator posture gates on the org flag alone with the
|
|
573
|
+
* empty-string subject sentinel. The Organization is loaded independently
|
|
574
|
+
* of the preferences step: step independence over one saved read.
|
|
503
575
|
*/
|
|
504
576
|
export function newComposeRecalledMemoriesStep(
|
|
505
577
|
store: Store,
|
|
506
578
|
logger: Logger,
|
|
579
|
+
personAccounts?: AccountsByCaller,
|
|
507
580
|
): PipelineStep<CreateDesc> {
|
|
508
581
|
return {
|
|
509
582
|
name: "ComposeRecalledMemories",
|
|
@@ -513,16 +586,21 @@ export function newComposeRecalledMemoriesStep(
|
|
|
513
586
|
// Claim the server-owned field first.
|
|
514
587
|
execution.spec.recalledMemories = create(RecalledMemoriesSchema);
|
|
515
588
|
|
|
516
|
-
|
|
517
|
-
if (orgId === "") {
|
|
518
|
-
orgId = ctx.input.metadata?.org ?? "";
|
|
519
|
-
}
|
|
589
|
+
const orgId = orgIdOf(ctx);
|
|
520
590
|
if (orgId === "") {
|
|
521
591
|
logger.debug(
|
|
522
592
|
"No org on execution metadata, composing no recalled memories",
|
|
523
593
|
);
|
|
524
594
|
return;
|
|
525
595
|
}
|
|
596
|
+
// Only a person's own run recalls (the Java gate order: the caller
|
|
597
|
+
// first, before any read).
|
|
598
|
+
if (
|
|
599
|
+
personAccounts !== undefined &&
|
|
600
|
+
!isFirstPartyHumanOperator(ctx.callerIdentity)
|
|
601
|
+
) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
526
604
|
|
|
527
605
|
let org: Organization;
|
|
528
606
|
try {
|
|
@@ -555,9 +633,27 @@ export function newComposeRecalledMemoriesStep(
|
|
|
555
633
|
return;
|
|
556
634
|
}
|
|
557
635
|
|
|
636
|
+
// The subject whose facts are recalled: the run's person, whose own
|
|
637
|
+
// switch must be on too, or the single-operator sentinel.
|
|
638
|
+
let subject = "";
|
|
639
|
+
if (personAccounts !== undefined) {
|
|
640
|
+
const person = await runPersonOf(
|
|
641
|
+
personAccounts,
|
|
642
|
+
ctx.callerIdentity,
|
|
643
|
+
logger,
|
|
644
|
+
"Failed to load the run's person for recalled memories - degrading to disabled (best-effort contract)",
|
|
645
|
+
);
|
|
646
|
+
if (person?.spec?.preferences?.memoryEnabled !== true) {
|
|
647
|
+
// A person who has not opted in is normal operation, not
|
|
648
|
+
// degradation; no log.
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
subject = person.metadata?.id ?? "";
|
|
652
|
+
}
|
|
653
|
+
|
|
558
654
|
let facts;
|
|
559
655
|
try {
|
|
560
|
-
facts = await loadConfirmedFacts(store, orgId);
|
|
656
|
+
facts = await loadConfirmedFacts(store, orgId, subject);
|
|
561
657
|
} catch (error) {
|
|
562
658
|
// Enabled stays false: a broken recall must not offer the
|
|
563
659
|
// remember tool (the enabled bit doubles as the tool signal).
|
|
@@ -582,13 +678,17 @@ export function newComposeRecalledMemoriesStep(
|
|
|
582
678
|
}
|
|
583
679
|
|
|
584
680
|
/**
|
|
585
|
-
* Scans the memory kind for
|
|
586
|
-
*
|
|
587
|
-
* memory_id + content (the id is the
|
|
588
|
-
* addressable record). Undecodable rows are
|
|
589
|
-
* not take recall down.
|
|
681
|
+
* Scans the memory kind for `subject`'s confirmed records in the org,
|
|
682
|
+
* oldest-first — the run's person's account id, or the single-operator
|
|
683
|
+
* "" sentinel. Facts carry only memory_id + content (the id is the
|
|
684
|
+
* transparency link back to the addressable record). Undecodable rows are
|
|
685
|
+
* skipped — one bad record must not take recall down.
|
|
590
686
|
*/
|
|
591
|
-
async function loadConfirmedFacts(
|
|
687
|
+
async function loadConfirmedFacts(
|
|
688
|
+
store: Store,
|
|
689
|
+
orgId: string,
|
|
690
|
+
subject: string,
|
|
691
|
+
) {
|
|
592
692
|
const rows = await store.listResources(ApiResourceKind.memory);
|
|
593
693
|
const memories: Memory[] = [];
|
|
594
694
|
for (const data of rows) {
|
|
@@ -601,7 +701,7 @@ async function loadConfirmedFacts(store: Store, orgId: string) {
|
|
|
601
701
|
if ((memory.metadata?.org ?? "") !== orgId) {
|
|
602
702
|
continue;
|
|
603
703
|
}
|
|
604
|
-
if ((memory.spec?.subjectIdentityAccountId ?? "") !==
|
|
704
|
+
if ((memory.spec?.subjectIdentityAccountId ?? "") !== subject) {
|
|
605
705
|
continue;
|
|
606
706
|
}
|
|
607
707
|
if (
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pins the store-fault contract of the agent instance's
|
|
3
|
-
* load: a typed ResourceNotFoundError answers NotFound with
|
|
4
|
-
* pinned copy (`agent instance not found: <id>`), and any other
|
|
5
|
-
* is an infrastructure fault answered as a sanitized Internal,
|
|
6
|
-
* NotFound that tells a client the instance does not exist
|
|
7
|
-
* (stigmer/stigmer#1345).
|
|
2
|
+
* Pins the store-fault contract of the agent instance's two loads. The
|
|
3
|
+
* visibility-update load: a typed ResourceNotFoundError answers NotFound with
|
|
4
|
+
* the domain's pinned copy (`agent instance not found: <id>`), and any other
|
|
5
|
+
* store failure is an infrastructure fault answered as a sanitized Internal,
|
|
6
|
+
* never a NotFound that tells a client the instance does not exist
|
|
7
|
+
* (stigmer/stigmer#1345). Create's parent-agent load reads the agent through
|
|
8
|
+
* the in-process query client, so the store's typed miss arrives as a Connect
|
|
9
|
+
* NotFound: that answers the pinned `Agent not found: <id>`, and any other
|
|
10
|
+
* failure an Internal, never a missing parent (stigmer/stigmer#1351).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Both surfaces are reached through the registered handler on an in-process
|
|
13
|
+
* router. The composed suites reach only a real store, which cannot fail
|
|
14
|
+
* selectively, so the visibility update runs against a store whose read
|
|
15
|
+
* throws and create against a parent loader whose read throws. Whatever the
|
|
16
|
+
* surface does not need is untouchable: a load that fails must stop the call
|
|
17
|
+
* before anything past it runs.
|
|
14
18
|
*
|
|
15
|
-
* Out of scope: the NotFound
|
|
16
|
-
* parent-agent load, which folds every RPC error into NotFound
|
|
17
|
-
* (stigmer/stigmer#1351).
|
|
19
|
+
* Out of scope: the NotFound copies themselves (wire contract).
|
|
18
20
|
*/
|
|
19
|
-
import type { Client
|
|
20
|
-
import {
|
|
21
|
+
import type { Client } from "@connectrpc/connect";
|
|
22
|
+
import {
|
|
23
|
+
Code,
|
|
24
|
+
ConnectError,
|
|
25
|
+
createClient,
|
|
26
|
+
createRouterTransport,
|
|
27
|
+
} from "@connectrpc/connect";
|
|
21
28
|
import { describe, expect, it } from "vitest";
|
|
22
29
|
|
|
23
30
|
import { AgentInstanceCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/command_pb";
|
|
@@ -31,11 +38,13 @@ import {
|
|
|
31
38
|
failingStore,
|
|
32
39
|
untouchable,
|
|
33
40
|
} from "../../../pipeline/__tests__/support.js";
|
|
41
|
+
import { INTERNAL_FALLBACK_MESSAGE } from "../../../pipeline/pipeline.js";
|
|
34
42
|
import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
|
|
35
43
|
import { ResourceNotFoundError } from "../../../store/interface.js";
|
|
36
44
|
import type { Store } from "../../../store/interface.js";
|
|
37
45
|
|
|
38
46
|
import { registerAgentInstanceServices } from "../controller.js";
|
|
47
|
+
import type { ParentAgentLoaderProvider } from "../steps.js";
|
|
39
48
|
|
|
40
49
|
const silentLogger = createLogger({
|
|
41
50
|
level: "error",
|
|
@@ -44,22 +53,34 @@ const silentLogger = createLogger({
|
|
|
44
53
|
});
|
|
45
54
|
|
|
46
55
|
const INSTANCE_ID = "ain_storefault";
|
|
56
|
+
const AGENT_ID = "agt_storefault";
|
|
47
57
|
|
|
48
58
|
const NOT_FOUND_COPY = `agent instance not found: ${INSTANCE_ID}`;
|
|
49
59
|
const LOAD_FAULT_COPY = "failed to load agent instance";
|
|
60
|
+
const PARENT_NOT_FOUND_COPY = `Agent not found: ${AGENT_ID}`;
|
|
61
|
+
const PARENT_LOAD_FAULT_COPY = "failed to load parent agent";
|
|
50
62
|
|
|
51
63
|
const MISSING = (): Error =>
|
|
52
64
|
new ResourceNotFoundError(`agent_instance/${INSTANCE_ID}`);
|
|
53
65
|
const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
|
|
54
66
|
|
|
67
|
+
/** What the agent query's `get` answers for a missing and a faulted agent. */
|
|
68
|
+
const PARENT_MISSING = (): Error =>
|
|
69
|
+
new ConnectError(`Agent not found: ${AGENT_ID}`, Code.NotFound);
|
|
70
|
+
const PARENT_FAULTED = (): Error =>
|
|
71
|
+
new ConnectError(INTERNAL_FALLBACK_MESSAGE, Code.Internal);
|
|
72
|
+
|
|
55
73
|
/**
|
|
56
74
|
* The registered handlers on an in-process router: the verifier chain stamps
|
|
57
75
|
* the trusted-local caller and the apiresource interceptor the kind, then
|
|
58
76
|
* Authorize and ValidateProto run and the load is the first step that reads
|
|
59
|
-
* the store.
|
|
77
|
+
* the store or the parent.
|
|
60
78
|
*/
|
|
61
79
|
function instanceCommand(
|
|
62
80
|
store: Store,
|
|
81
|
+
parentAgentLoader: ParentAgentLoaderProvider = untouchable(
|
|
82
|
+
"parentAgentLoader",
|
|
83
|
+
),
|
|
63
84
|
): Client<typeof AgentInstanceCommandController> {
|
|
64
85
|
const transport = createRouterTransport(
|
|
65
86
|
(router) => {
|
|
@@ -68,7 +89,7 @@ function instanceCommand(
|
|
|
68
89
|
logger: silentLogger,
|
|
69
90
|
authorizer: newPermissiveSingleTeamAuthorizer(),
|
|
70
91
|
authorizationLifecycle: undefined,
|
|
71
|
-
parentAgentLoader
|
|
92
|
+
parentAgentLoader,
|
|
72
93
|
listReadScope: undefined,
|
|
73
94
|
});
|
|
74
95
|
},
|
|
@@ -108,3 +129,33 @@ describe("updateVisibility — LoadInstanceForVisibilityUpdate", () => {
|
|
|
108
129
|
expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
|
|
109
130
|
});
|
|
110
131
|
});
|
|
132
|
+
|
|
133
|
+
describe("create — LoadParentAgent", () => {
|
|
134
|
+
function surfaceError(parentError: Error): Promise<ConnectError> {
|
|
135
|
+
const failingParent: ParentAgentLoaderProvider = () => ({
|
|
136
|
+
get: () => Promise.reject(parentError),
|
|
137
|
+
});
|
|
138
|
+
return errorOf(() =>
|
|
139
|
+
instanceCommand(untouchable("store"), failingParent).create({
|
|
140
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
141
|
+
kind: "AgentInstance",
|
|
142
|
+
metadata: { name: "Store Fault Instance", org: "org_storefault" },
|
|
143
|
+
spec: { agentId: AGENT_ID },
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
it("a missing parent agent answers NotFound with the pinned copy", async () => {
|
|
149
|
+
const error = await surfaceError(PARENT_MISSING());
|
|
150
|
+
|
|
151
|
+
expect(error.code).toBe(Code.NotFound);
|
|
152
|
+
expect(error.rawMessage).toBe(PARENT_NOT_FOUND_COPY);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("any other parent-load failure answers a sanitized Internal", async () => {
|
|
156
|
+
const error = await surfaceError(PARENT_FAULTED());
|
|
157
|
+
|
|
158
|
+
expect(error.code).toBe(Code.Internal);
|
|
159
|
+
expect(error.rawMessage).toBe(PARENT_LOAD_FAULT_COPY);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -10,8 +10,14 @@
|
|
|
10
10
|
* Proven by agentinstance.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
11
11
|
* __tests__/agentinstance.test.ts and __tests__/store-faults.test.ts.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Every chain opens with Authorize, and create asks
|
|
14
|
+
* can_create_agent_instance on the organization, then can_create_instance
|
|
15
|
+
* on the parent agent (AuthorizeResolvedTarget); create, delete and
|
|
16
|
+
* updateVisibility run the shared tuple-lifecycle steps against the
|
|
17
|
+
* composed lifecycle; getByAgent and list narrow through the composed list
|
|
18
|
+
* read scope (docs/authorization-coverage.md §7).
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NO same-org rule on create, unlike WorkflowInstance:
|
|
15
21
|
* an agent is a shareable blueprint, and one agent legitimately has
|
|
16
22
|
* instances in several orgs (the marketplace case). What governs a
|
|
17
23
|
* cross-org instance in both editions is the create lane's authorization
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* contracts: the parent-agent edge, the immutable agent_id, the
|
|
7
7
|
* default-instance visibility guard, and the org/label list filters.
|
|
8
8
|
*/
|
|
9
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
9
10
|
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
10
11
|
import type { DescMessage } from "@bufbuild/protobuf";
|
|
11
12
|
|
|
@@ -69,7 +70,9 @@ export const PARENT_AGENT_KEY = "parent_agent";
|
|
|
69
70
|
* an agent is a shareable blueprint and one agent legitimately has
|
|
70
71
|
* instances in several orgs (the marketplace case) — the instance's
|
|
71
72
|
* organization is the caller's, and the check below asks the caller's
|
|
72
|
-
* standing there.
|
|
73
|
+
* standing there. The agent is read through the in-process query client, so
|
|
74
|
+
* a missing one arrives as a Connect NotFound; any other failure is a fault
|
|
75
|
+
* answered as Internal, never a missing parent.
|
|
73
76
|
*/
|
|
74
77
|
export function newLoadParentAgentStep(
|
|
75
78
|
agentLoader: ParentAgentLoaderProvider,
|
|
@@ -88,11 +91,14 @@ export function newLoadParentAgentStep(
|
|
|
88
91
|
try {
|
|
89
92
|
parentAgent = await agentLoader().get(agentId);
|
|
90
93
|
} catch (error) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) {
|
|
95
|
+
logger.warn("Parent agent not found", {
|
|
96
|
+
agentId,
|
|
97
|
+
error: error.rawMessage,
|
|
98
|
+
});
|
|
99
|
+
throw notFoundError("Agent", agentId);
|
|
100
|
+
}
|
|
101
|
+
throw internalError(error, "failed to load parent agent");
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
logger.debug("Loaded parent agent", {
|
|
@@ -13,10 +13,15 @@
|
|
|
13
13
|
* cascade of same-org shares lives in the AGENT domain (ported with #6);
|
|
14
14
|
* share delete itself cascades nothing.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Every chain opens with Authorize. create asks can_edit on the referenced
|
|
17
|
+
* agent, then can_create_agent_share (AuthorizeResolvedTarget); create and
|
|
18
|
+
* delete run the shared tuple-lifecycle steps against the composed
|
|
19
|
+
* lifecycle; getByReference authorizes the loaded share as `get` would;
|
|
20
|
+
* getByAgent and list narrow through the composed list read scope. The
|
|
21
|
+
* public share-link read skips the Authorizer through is_public, and the
|
|
22
|
+
* member read asks can_view on the organization before the share loads
|
|
23
|
+
* (AuthorizeMemberAudience). Per-RPC posture:
|
|
24
|
+
* docs/authorization-coverage.md §9.
|
|
20
25
|
*
|
|
21
26
|
* Proven by agentshare.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
22
27
|
* __tests__/agentshare.test.ts and __tests__/store-faults.test.ts.
|
|
@@ -657,9 +662,9 @@ function newLoadSharesByAgentStep(
|
|
|
657
662
|
const LIST_RESULT_KEY = "listResult";
|
|
658
663
|
|
|
659
664
|
/**
|
|
660
|
-
* List — org + labels filter (AND semantics), newest first
|
|
661
|
-
*
|
|
662
|
-
* list.go records.
|
|
665
|
+
* List — org + labels filter (AND semantics), newest first, narrowed to
|
|
666
|
+
* the caller's authorized rows by the composed list read scope.
|
|
667
|
+
* Unpaginated, exactly as Go's list.go records.
|
|
663
668
|
*/
|
|
664
669
|
async function list(
|
|
665
670
|
deps: AgentShareControllerDeps,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The artifact HTTP file server — ports the local-download lane from Go
|
|
3
3
|
* pkg/server/server.go (artifactDownloadHandler + the boot block, lines
|
|
4
|
-
* 849–926): a second listener on 127.0.0.1:ARTIFACT_HTTP_PORT (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 849–926): a second listener on 127.0.0.1:ARTIFACT_HTTP_PORT (unset: the
|
|
5
|
+
* unified port + 1, ephemeral beside an ephemeral unified port — the rule is
|
|
6
|
+
* boot/artifact-lane.ts), started only when artifact storage is LOCAL,
|
|
7
|
+
* serving GET /<key> as the exact bytes LocalArtifactStorage wrote. Unlike
|
|
8
|
+
* Go, a bind failure fails the boot: the composition binds the lane before
|
|
9
|
+
* SERVING and lets listen()'s rejection stand (stigmer#1089). The lane
|
|
7
10
|
* dissolved into its owning domain per the ratified D4 decision — it is
|
|
8
11
|
* NOT a unified-port lane (Go runs it as a separate listener, and so does
|
|
9
12
|
* this port).
|
|
@@ -39,7 +42,8 @@ export interface ArtifactFileServerOptions {
|
|
|
39
42
|
|
|
40
43
|
export interface ArtifactFileServer {
|
|
41
44
|
/**
|
|
42
|
-
* Binds <host>:<port> (
|
|
45
|
+
* Binds <host>:<port> (port 0 picks ephemeral) and resolves the port it
|
|
46
|
+
* bound; a bind failure rejects, and the composition fails the boot on it.
|
|
43
47
|
* The host is the composition root's call: 127.0.0.1 everywhere by
|
|
44
48
|
* default (ARTIFACT_HTTP_HOST, DD-013) — download URLs are minted for
|
|
45
49
|
* the local machine — and 0.0.0.0 only inside the official container
|
|
@@ -14,9 +14,14 @@
|
|
|
14
14
|
* not_search_indexed — configuration reached through its parent surface,
|
|
15
15
|
* not a library artifact), so no index steps appear in any chain.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Every chain opens with Authorize; create and delete run the shared
|
|
18
|
+
* tuple-lifecycle steps against the composed lifecycle; getByReference
|
|
19
|
+
* loads, then authorizes the loaded app exactly as `get` would
|
|
20
|
+
* (AuthorizeResolvedTarget). listByOrg does not yet narrow through the
|
|
21
|
+
* list read scope, although the model restricts viewing to owners,
|
|
22
|
+
* explicit grants and organization admins
|
|
23
|
+
* (fga/model/agentic/channel_app.fga, stigmer/stigmer#1384). Per-RPC
|
|
24
|
+
* posture: docs/authorization-coverage.md §13.
|
|
20
25
|
*
|
|
21
26
|
* Proven by channelapp.conformance.test.ts (CONFORMANCE_TARGET=local)
|
|
22
27
|
* and __tests__/channelapp.test.ts.
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
* Proven by environment.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
12
12
|
* __tests__/environment.test.ts and __tests__/store-faults.test.ts.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Every chain opens with Authorize; create, delete and updateVisibility run
|
|
15
|
+
* the shared tuple-lifecycle steps against the composed lifecycle;
|
|
16
|
+
* getByReference loads, then authorizes the loaded environment exactly as
|
|
17
|
+
* `get` would (AuthorizeResolvedTarget); list narrows through the composed
|
|
18
|
+
* list read scope. The visibility ceiling (org — secrets never resolve
|
|
19
|
+
* across the org boundary) and share restrictions come from the same proto
|
|
20
|
+
* config in every edition, keeping the error contract identical. Per-RPC
|
|
21
|
+
* posture: docs/authorization-coverage.md §3.
|
|
19
22
|
*/
|
|
20
23
|
import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
|
|
21
24
|
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
@@ -734,9 +737,9 @@ async function getSecretValue(
|
|
|
734
737
|
const LIST_RESULT_KEY = "listResult";
|
|
735
738
|
|
|
736
739
|
/**
|
|
737
|
-
* List — org + labels filter with per-item redaction
|
|
738
|
-
*
|
|
739
|
-
* exactly as Go's list.go records.
|
|
740
|
+
* List — org + labels filter with per-item redaction, narrowed to the
|
|
741
|
+
* caller's authorized rows by the composed list read scope. Unpaginated
|
|
742
|
+
* (returns all matches), exactly as Go's list.go records.
|
|
740
743
|
*/
|
|
741
744
|
async function list(
|
|
742
745
|
deps: EnvironmentControllerDeps,
|
|
@@ -25,10 +25,13 @@
|
|
|
25
25
|
* (the decrypt-lane matrix conformance deliberately never exercises —
|
|
26
26
|
* its harness authenticates as a user).
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Every chain opens with Authorize; create asks can_create_execution_in on
|
|
29
|
+
* the organization (AuthorizeCreate), and create and delete run the shared
|
|
30
|
+
* tuple-lifecycle steps against the composed lifecycle; getByReference
|
|
31
|
+
* loads, then authorizes the loaded context exactly as `get` would
|
|
32
|
+
* (AuthorizeResolvedTarget). The redaction and decrypt-lane contracts are
|
|
33
|
+
* the same in every edition, keeping the error contract identical. Per-RPC
|
|
34
|
+
* posture: docs/authorization-coverage.md §5.
|
|
32
35
|
*/
|
|
33
36
|
import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
|
|
34
37
|
|