@noodleseed/one 0.174.0 → 0.175.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.
- package/dist/commands/solutions-activity.js +1 -1
- package/dist/commands/solutions-activity.js.map +1 -1
- package/dist/commands/solutions-drafts.d.ts.map +1 -1
- package/dist/commands/solutions-drafts.js +36 -9
- package/dist/commands/solutions-drafts.js.map +1 -1
- package/dist/commands/solutions-native-lifecycle.d.ts +5 -0
- package/dist/commands/solutions-native-lifecycle.d.ts.map +1 -0
- package/dist/commands/solutions-native-lifecycle.js +77 -0
- package/dist/commands/solutions-native-lifecycle.js.map +1 -0
- package/dist/commands/solutions-ops.d.ts.map +1 -1
- package/dist/commands/solutions-ops.js +3 -0
- package/dist/commands/solutions-ops.js.map +1 -1
- package/dist/commands/solutions-workspace.d.ts.map +1 -1
- package/dist/commands/solutions-workspace.js +36 -11
- package/dist/commands/solutions-workspace.js.map +1 -1
- package/dist/commands/whatsapp-output.d.ts.map +1 -1
- package/dist/commands/whatsapp-output.js +20 -4
- package/dist/commands/whatsapp-output.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.d.ts +16 -9
- package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.js +5 -2
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +9 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +5 -5
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +9 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +13 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +5 -3
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +5 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +7 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +14 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-capture.js +104 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-judgment.js +93 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.d.ts +105 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.js +183 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-model-call.js +38 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.js +109 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.d.ts +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.js +44 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.js +86 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.d.ts +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.js +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +20 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +38 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +5 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +5 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/authoring/dist/interaction.d.ts +49 -0
- package/node_modules/@noodle-borg/authoring/dist/interaction.js +25 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +4 -0
- package/node_modules/@noodle-borg/authoring/package.json +6 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-drafts.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-native-lifecycle.js +43 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-records.js +154 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-workspace.js +8 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +2 -149
- package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +33 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/compile.js +5 -0
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/index.d.ts +2 -2
- package/node_modules/@noodle-borg/compiler/dist/index.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.d.ts +41 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.js +62 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-validation.js +220 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +60 -4
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +7 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +9 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +58 -10
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.d.ts +8 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.js +136 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.d.ts +7 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.js +16 -24
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.d.ts +4 -2
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.js +8 -6
- package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.d.ts +2 -1
- package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.js +13 -8
- package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.d.ts +13 -4
- package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.js +23 -7
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -1
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/module/dist/platform-human-identity.d.ts +5 -2
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +16 -4
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -6
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/application-activity.js +85 -63
- package/node_modules/@noodle-borg/service/dist/application-drafts/contracts.js +9 -0
- package/node_modules/@noodle-borg/service/dist/application-drafts/paths.js +2 -2
- package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/contracts.d.ts +18 -4
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-business-pages.js +3 -5
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-installation-lifecycle.js +8 -6
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-native-lifecycle.js +80 -0
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +27 -31
- package/node_modules/@noodle-borg/service/dist/business-information/memory-native-retention.js +29 -0
- package/node_modules/@noodle-borg/service/dist/business-information/model.js +9 -2
- package/node_modules/@noodle-borg/service/dist/business-information/native-query.js +3 -1
- package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.d.ts +27 -0
- package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.js +63 -0
- package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.d.ts +3 -1
- package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.js +9 -4
- package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.d.ts +16 -0
- package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.js +60 -0
- package/node_modules/@noodle-borg/service/dist/business-information/workspace-permissions.js +18 -0
- package/node_modules/@noodle-borg/service/dist/business-information-runtime.js +1 -0
- package/node_modules/@noodle-borg/service/dist/business-onboarding.js +16 -3
- package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.d.ts +110 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/permissions.d.ts +7 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/store.d.ts +102 -0
- package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +70 -8
- package/node_modules/@noodle-borg/service/dist/channels/capability-report.js +73 -0
- package/node_modules/@noodle-borg/service/dist/channels/collection-turn.js +232 -0
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +1 -0
- package/node_modules/@noodle-borg/service/dist/channels/messaging-eligibility.js +45 -0
- package/node_modules/@noodle-borg/service/dist/channels/reply-buttons.js +23 -0
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +127 -33
- package/node_modules/@noodle-borg/service/dist/native-record-connector.js +19 -4
- package/node_modules/@noodle-borg/service/dist/routes/application-drafts.js +44 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-authoring-dispatch.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-access.js +39 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-connections.js +10 -4
- package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +11 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-notice.js +6 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-pages.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-record-create.js +7 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-record-reads.js +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-records.js +332 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-settings.js +6 -3
- package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +9 -5
- package/node_modules/@noodle-borg/service/dist/routes/business-information.js +12 -356
- package/node_modules/@noodle-borg/service/dist/routes/business-native-lifecycle.js +46 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-workspaces.js +14 -1
- package/node_modules/@noodle-borg/service/dist/service.js +1 -0
- package/node_modules/@noodle-borg/service/dist/web-capabilities.js +3 -1
- package/node_modules/@noodle-borg/service/package.json +2 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.d.ts +59 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.js +38 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +144 -50
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +44 -4
- package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.d.ts +70 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.js +31 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.d.ts +81 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.js +39 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +26 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +24 -1
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -4,6 +4,7 @@ import { ApplicationActivityListResponseSchema, ApplicationActivityPreviewRespon
|
|
|
4
4
|
import { resolveApplicationRuntimeTarget } from './application-runtime-target.js';
|
|
5
5
|
import { createOperationCoordinationPort, } from './operation-coordination.js';
|
|
6
6
|
import { createOperationEvidencePort, } from './operation-evidence.js';
|
|
7
|
+
const runLocal = (operation) => operation();
|
|
7
8
|
const POLICY_MESSAGES = {
|
|
8
9
|
activity_conflict: 'History settings or plan changed. Reload before continuing.',
|
|
9
10
|
activity_invalid: 'Activity paging or retention is invalid for the current policy.',
|
|
@@ -25,8 +26,8 @@ export class ApplicationActivity {
|
|
|
25
26
|
constructor(options) {
|
|
26
27
|
this.options = options;
|
|
27
28
|
}
|
|
28
|
-
/**
|
|
29
|
-
async project(scope, action, input) {
|
|
29
|
+
/** Callers authorize before/after projection and fence each local effect with current authority. */
|
|
30
|
+
async project(scope, action, input, local = runLocal) {
|
|
30
31
|
const coordinating = action.startsWith('coordination');
|
|
31
32
|
const invalid = () => new ActivityPolicyError(coordinating ? 'coordination_invalid' : 'activity_invalid');
|
|
32
33
|
const allowed = action === 'coordination-list'
|
|
@@ -51,16 +52,17 @@ export class ApplicationActivity {
|
|
|
51
52
|
const store = this.options.coordination;
|
|
52
53
|
if (!store)
|
|
53
54
|
throw new ActivityPolicyError('coordination_unavailable');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
const resolved = await local(async () => {
|
|
56
|
+
try {
|
|
57
|
+
return await store.resolve(scope, parsed.data.resource, parsed.data.token, {
|
|
58
|
+
reviewer: input.reviewer,
|
|
59
|
+
reason: parsed.data.reason,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new ActivityPolicyError('coordination_unavailable');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
64
66
|
if (!resolved)
|
|
65
67
|
throw new ActivityPolicyError('coordination_conflict');
|
|
66
68
|
return mutation(OperationCoordinationResolveResponseSchema.parse({ ok: true, data: { resolved: true } }), 'config.operation.coordination_resolved', { resource: parsed.data.resource });
|
|
@@ -77,52 +79,56 @@ export class ApplicationActivity {
|
|
|
77
79
|
const store = this.options.coordination;
|
|
78
80
|
if (!store)
|
|
79
81
|
throw new ActivityPolicyError('coordination_unavailable');
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
resource,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
82
|
+
return local(async () => {
|
|
83
|
+
try {
|
|
84
|
+
const limit = parsed.data.limit ?? 100;
|
|
85
|
+
const records = await store.list(scope, limit, parsed.data.beforeResource);
|
|
86
|
+
if (records.some((record) => canonicalJson(record.scope) !== canonicalJson(scope)))
|
|
87
|
+
throw new Error('Coordination scope mismatch');
|
|
88
|
+
return {
|
|
89
|
+
response: OperationCoordinationListResponseSchema.parse({
|
|
90
|
+
ok: true,
|
|
91
|
+
data: {
|
|
92
|
+
records: records.map(({ resource, token, reference, operationDigest, startedAt, deadline, state, }) => ({
|
|
93
|
+
resource,
|
|
94
|
+
token,
|
|
95
|
+
reference,
|
|
96
|
+
operationDigest,
|
|
97
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
98
|
+
deadline: new Date(deadline).toISOString(),
|
|
99
|
+
state,
|
|
100
|
+
})),
|
|
101
|
+
...(records.length === limit
|
|
102
|
+
? { nextBeforeResource: records.at(-1)?.resource }
|
|
103
|
+
: {}),
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new ActivityPolicyError('coordination_unavailable');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
106
112
|
}
|
|
107
113
|
if (action === 'save-settings') {
|
|
108
114
|
const parsed = ApplicationActivitySettingsSaveRequestSchema.safeParse(input.body);
|
|
109
115
|
if (!parsed.success)
|
|
110
116
|
throw invalid();
|
|
111
|
-
const data = await this.save(scope, parsed.data);
|
|
117
|
+
const data = await this.save(scope, parsed.data, local);
|
|
112
118
|
return mutation(ApplicationActivitySettingsResponseSchema.parse({ ok: true, data }), 'config.activity.retention_changed', { retentionDays: data.retentionDays });
|
|
113
119
|
}
|
|
114
120
|
if (action === 'preview')
|
|
115
121
|
return {
|
|
116
122
|
response: ApplicationActivityPreviewResponseSchema.parse({
|
|
117
123
|
ok: true,
|
|
118
|
-
data: await this.preview(scope),
|
|
124
|
+
data: await this.preview(scope, local),
|
|
119
125
|
}),
|
|
120
126
|
};
|
|
121
127
|
if (action === 'settings')
|
|
122
128
|
return {
|
|
123
129
|
response: ApplicationActivitySettingsResponseSchema.parse({
|
|
124
130
|
ok: true,
|
|
125
|
-
data: (await this.settings(scope, input.canEdit)).projection,
|
|
131
|
+
data: (await this.settings(scope, input.canEdit, local)).projection,
|
|
126
132
|
}),
|
|
127
133
|
};
|
|
128
134
|
const limit = parameters.has('limit') ? Number(parameters.get('limit')) : 50;
|
|
@@ -139,11 +145,11 @@ export class ApplicationActivity {
|
|
|
139
145
|
purpose: action,
|
|
140
146
|
limit,
|
|
141
147
|
...(cursor === undefined ? {} : { cursor }),
|
|
142
|
-
}),
|
|
148
|
+
}, local),
|
|
143
149
|
}),
|
|
144
150
|
};
|
|
145
151
|
}
|
|
146
|
-
async settings(scope, canEdit) {
|
|
152
|
+
async settings(scope, canEdit, local = runLocal) {
|
|
147
153
|
const allowance = await this.options.allowance(scope.org);
|
|
148
154
|
if (!allowance ||
|
|
149
155
|
!validDays(allowance.maximumDays) ||
|
|
@@ -151,11 +157,14 @@ export class ApplicationActivity {
|
|
|
151
157
|
allowance.defaultDays > allowance.maximumDays ||
|
|
152
158
|
!allowance.revision)
|
|
153
159
|
throw new ActivityPolicyError('activity_unavailable');
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
const setting = await local(async () => {
|
|
161
|
+
let value = await this.options.store.readRetention(scope);
|
|
162
|
+
if (!value) {
|
|
163
|
+
await this.options.store.setRetention(scope, allowance.defaultDays, undefined);
|
|
164
|
+
value = await this.options.store.readRetention(scope);
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
167
|
+
});
|
|
159
168
|
if (!setting || !validDays(setting.days))
|
|
160
169
|
throw new ActivityPolicyError('activity_unavailable');
|
|
161
170
|
return {
|
|
@@ -169,28 +178,28 @@ export class ApplicationActivity {
|
|
|
169
178
|
},
|
|
170
179
|
};
|
|
171
180
|
}
|
|
172
|
-
async save(scope, input) {
|
|
173
|
-
const current = await this.settings(scope, true);
|
|
181
|
+
async save(scope, input, local = runLocal) {
|
|
182
|
+
const current = await this.settings(scope, true, local);
|
|
174
183
|
if (current.projection.revision !== input.expectedRevision)
|
|
175
184
|
throw new ActivityPolicyError('activity_conflict');
|
|
176
185
|
if (!validDays(input.retentionDays) || input.retentionDays > current.allowance.maximumDays)
|
|
177
186
|
throw new ActivityPolicyError('activity_invalid');
|
|
178
|
-
if (!(await this.options.store.setRetention(scope, input.retentionDays, current.setting.revision)))
|
|
187
|
+
if (!(await local(() => this.options.store.setRetention(scope, input.retentionDays, current.setting.revision))))
|
|
179
188
|
throw new ActivityPolicyError('activity_conflict');
|
|
180
|
-
return (await this.settings(scope, true)).projection;
|
|
189
|
+
return (await this.settings(scope, true, local)).projection;
|
|
181
190
|
}
|
|
182
|
-
async page(scope, input) {
|
|
191
|
+
async page(scope, input, local = runLocal) {
|
|
183
192
|
if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 100)
|
|
184
193
|
throw new ActivityPolicyError('activity_invalid');
|
|
185
|
-
const policy = (await this.settings(scope, false)).projection;
|
|
194
|
+
const policy = (await this.settings(scope, false, local)).projection;
|
|
186
195
|
const binding = this.hash({
|
|
187
196
|
purpose: `activity-${input.purpose}-v1`,
|
|
188
197
|
scope,
|
|
189
198
|
revision: policy.revision,
|
|
190
199
|
limit: input.limit,
|
|
191
200
|
});
|
|
192
|
-
const records = await this.options.store.list(scope, this.options.now?.() ?? Date.now(), policy.
|
|
193
|
-
if ((await this.settings(scope, false)).projection.revision !== policy.revision)
|
|
201
|
+
const records = await local(() => this.options.store.list(scope, this.options.now?.() ?? Date.now(), policy.retentionDays, input.limit + 1, decodeActivityCursor(input.cursor, binding)));
|
|
202
|
+
if ((await this.settings(scope, false, local)).projection.revision !== policy.revision)
|
|
194
203
|
throw new ActivityPolicyError('activity_conflict');
|
|
195
204
|
const page = records.slice(0, input.limit);
|
|
196
205
|
const last = page.at(-1);
|
|
@@ -208,7 +217,7 @@ export class ApplicationActivity {
|
|
|
208
217
|
: { completedAt: new Date(completedAt).toISOString() }),
|
|
209
218
|
...(reference === undefined ? {} : { reference }),
|
|
210
219
|
})),
|
|
211
|
-
historyDays: policy.
|
|
220
|
+
historyDays: policy.retentionDays,
|
|
212
221
|
...(records.length > page.length && last
|
|
213
222
|
? {
|
|
214
223
|
nextCursor: Buffer.from(JSON.stringify({ binding, startedAt: last.startedAt, id: last.id })).toString('base64url'),
|
|
@@ -224,7 +233,7 @@ export class ApplicationActivity {
|
|
|
224
233
|
revision(scope, setting, allowance) {
|
|
225
234
|
return sha256Canonical({ scope, setting, allowance });
|
|
226
235
|
}
|
|
227
|
-
async preview(scope) {
|
|
236
|
+
async preview(scope, local = runLocal) {
|
|
228
237
|
const unavailable = () => {
|
|
229
238
|
throw new ActivityPolicyError('activity_unavailable');
|
|
230
239
|
};
|
|
@@ -254,16 +263,28 @@ export class ApplicationActivity {
|
|
|
254
263
|
!validDays(maximumDays) ||
|
|
255
264
|
maximumDays >= allowance.maximumDays))
|
|
256
265
|
throw new ActivityPolicyError('activity_unavailable');
|
|
257
|
-
|
|
266
|
+
// A comparison is read-only: use the verified default without initializing a setting.
|
|
267
|
+
const readSetting = () => local(() => this.options.store.readRetention(scope).catch(unavailable));
|
|
268
|
+
const setting = await readSetting();
|
|
269
|
+
if (setting && !validDays(setting.days))
|
|
270
|
+
throw new ActivityPolicyError('activity_unavailable');
|
|
271
|
+
const selectedDays = setting?.days ?? allowance.defaultDays;
|
|
272
|
+
const counts = await local(() => this.options.store
|
|
258
273
|
.preview(scope, {
|
|
259
274
|
asOf,
|
|
260
275
|
paidPeriodEnd,
|
|
261
|
-
currentMaximumDays: allowance.maximumDays,
|
|
262
|
-
scenarios: preview.scenarios,
|
|
276
|
+
currentMaximumDays: Math.min(selectedDays, allowance.maximumDays),
|
|
277
|
+
scenarios: preview.scenarios.map(({ id, maximumDays }) => ({
|
|
278
|
+
id,
|
|
279
|
+
maximumDays: Math.min(selectedDays, maximumDays),
|
|
280
|
+
})),
|
|
263
281
|
})
|
|
264
|
-
.catch(unavailable);
|
|
282
|
+
.catch(unavailable));
|
|
265
283
|
const current = await resolve();
|
|
266
|
-
|
|
284
|
+
const currentSetting = await readSetting();
|
|
285
|
+
if (current?.revision !== allowance.revision ||
|
|
286
|
+
currentSetting?.revision !== setting?.revision ||
|
|
287
|
+
currentSetting?.days !== setting?.days)
|
|
267
288
|
throw new ActivityPolicyError('activity_conflict');
|
|
268
289
|
return {
|
|
269
290
|
state: 'available',
|
|
@@ -274,6 +295,7 @@ export class ApplicationActivity {
|
|
|
274
295
|
purpose: 'activity-preview-v1',
|
|
275
296
|
scope,
|
|
276
297
|
allowance: allowance.revision,
|
|
298
|
+
setting: setting ?? null,
|
|
277
299
|
preview,
|
|
278
300
|
counts,
|
|
279
301
|
}),
|
|
@@ -8,4 +8,13 @@ export class ApplicationDraftError extends Error {
|
|
|
8
8
|
this.name = 'ApplicationDraftError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/** Service-facing availability only; source errors remain bounded validation reports. */
|
|
12
|
+
export class DraftValidationUnavailableError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code) {
|
|
15
|
+
super(code);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = 'DraftValidationUnavailableError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
11
20
|
//# sourceMappingURL=contracts.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { validateSlug } from '@noodle-borg/control-plane/portable';
|
|
2
2
|
import { ApplicationDraftIdSchema } from '@noodle-borg/wire-contracts';
|
|
3
3
|
export function parseApplicationDraftPath(path) {
|
|
4
|
-
const match = /^\/v1\/orgs\/([a-z0-9-]+)\/apps\/([a-z0-9-]+)\/drafts(?:\/([a-f0-9-]+)(?:\/(undo|history|diff))?)?$/.exec(path);
|
|
4
|
+
const match = /^\/v1\/orgs\/([a-z0-9-]+)\/apps\/([a-z0-9-]+)\/drafts(?:\/([a-f0-9-]+)(?:\/(undo|history|diff|validate))?)?$/.exec(path);
|
|
5
5
|
const org = match?.[1], app = match?.[2], id = match?.[3], action = match?.[4];
|
|
6
6
|
if (!org || !app || (id !== undefined && !ApplicationDraftIdSchema.safeParse(id).success))
|
|
7
7
|
return undefined;
|
|
@@ -15,7 +15,7 @@ export function parseApplicationDraftPath(path) {
|
|
|
15
15
|
return { org, app, ...(id ? { id } : {}), ...(action ? { action } : {}) };
|
|
16
16
|
}
|
|
17
17
|
export function applicationDraftMethods(ref) {
|
|
18
|
-
return ref.action === 'undo'
|
|
18
|
+
return ref.action === 'undo' || ref.action === 'validate'
|
|
19
19
|
? ['POST']
|
|
20
20
|
: ref.action
|
|
21
21
|
? ['GET']
|
|
@@ -120,7 +120,7 @@ export function createApplicationServingRuntime(registry, providerOptions, contr
|
|
|
120
120
|
const options = providerOptions;
|
|
121
121
|
const businessInformationStore = options.businessInformationStore;
|
|
122
122
|
const businessOnboarding = options.businessOnboarding !== undefined && businessInformationStore
|
|
123
|
-
? new BusinessOnboarding(options.businessOnboarding, controlPlane, businessInformationStore)
|
|
123
|
+
? new BusinessOnboarding(options.businessOnboarding, controlPlane, businessInformationStore, options.businessAuthoring?.workspaces)
|
|
124
124
|
: undefined;
|
|
125
125
|
const activateInstallation = (input) => activateSolutionInstallation(input, {
|
|
126
126
|
registry,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ArtifactVariableDeclaration, ManagedCollectionControls } from '@noodle-borg/compiler';
|
|
2
|
-
import type { ManagedRecordQuery } from '@noodle-borg/wire-contracts';
|
|
2
|
+
import type { EligibleBusinessAssignee, ManagedRecordQuery } from '@noodle-borg/wire-contracts';
|
|
3
|
+
import type { BusinessWorkspaceAccess } from '../business-workspaces/contracts.js';
|
|
3
4
|
import type { BusinessNoticeStore } from './business-notice.js';
|
|
4
5
|
import type { BusinessPageStore } from './business-page.js';
|
|
6
|
+
import type { NativeRecordLifecycleStore } from './native-record-lifecycle.js';
|
|
5
7
|
import type { BusinessPrincipalProvider } from './principal-authority.js';
|
|
8
|
+
import type { BusinessStaffAuthority } from './staff-authority.js';
|
|
6
9
|
export type JsonPrimitive = string | number | boolean | null;
|
|
7
10
|
export type JsonValue = JsonPrimitive | JsonObject | readonly JsonValue[];
|
|
8
11
|
export interface JsonObject {
|
|
@@ -86,6 +89,8 @@ export interface SolutionInstallation {
|
|
|
86
89
|
readonly managedCollections: readonly string[];
|
|
87
90
|
readonly definition: SolutionDefinitionSnapshot;
|
|
88
91
|
readonly retentionDays: ManagedRetentionDays;
|
|
92
|
+
/** Absent on legacy installations until reviewed lifecycle migration. */
|
|
93
|
+
readonly nativeRecordLifecycle?: 'explicit_erasure';
|
|
89
94
|
/** Public native-record intake exposure; authorized records remain operable while paused. */
|
|
90
95
|
readonly intakeActive: boolean;
|
|
91
96
|
/** Internal binding to the authoritative app incarnation; never a Portal configuration value. */
|
|
@@ -97,6 +102,7 @@ export interface SolutionInstallation {
|
|
|
97
102
|
readonly updatedBySubject: string;
|
|
98
103
|
}
|
|
99
104
|
export interface BusinessGrant {
|
|
105
|
+
readonly authorityVersion?: never;
|
|
100
106
|
readonly scope: InstallationScope;
|
|
101
107
|
readonly subject: string;
|
|
102
108
|
readonly email?: string;
|
|
@@ -108,6 +114,11 @@ export interface BusinessGrant {
|
|
|
108
114
|
readonly updatedBySubject: string;
|
|
109
115
|
readonly revokedAt?: string;
|
|
110
116
|
}
|
|
117
|
+
/** Request-scoped access projection. Versioned access is never persisted as an installation grant. */
|
|
118
|
+
export type BusinessStaffGrant = BusinessGrant | (BusinessWorkspaceAccess & {
|
|
119
|
+
readonly scope: InstallationScope;
|
|
120
|
+
readonly revokedAt?: never;
|
|
121
|
+
});
|
|
111
122
|
export interface BusinessInvitation {
|
|
112
123
|
readonly scope: InstallationScope;
|
|
113
124
|
readonly invitationId: string;
|
|
@@ -127,7 +138,8 @@ export interface BusinessInvitation {
|
|
|
127
138
|
readonly revokedBySubject?: string;
|
|
128
139
|
}
|
|
129
140
|
export interface ManagedRequestOrigin {
|
|
130
|
-
|
|
141
|
+
/** `messaging` is trusted channel provenance (ADR 0240); its reference names the channel, e.g. `whatsapp`. */
|
|
142
|
+
readonly kind: 'embedded' | 'mcp' | 'portal' | 'api' | 'import' | 'messaging';
|
|
131
143
|
readonly reference?: string;
|
|
132
144
|
}
|
|
133
145
|
export interface ManagedRequestNote {
|
|
@@ -157,7 +169,7 @@ export interface ManagedRequestRecord {
|
|
|
157
169
|
readonly assigneeSubject?: string;
|
|
158
170
|
readonly origin: ManagedRequestOrigin;
|
|
159
171
|
readonly revision: number;
|
|
160
|
-
readonly retentionExpiresAt: string;
|
|
172
|
+
readonly retentionExpiresAt: string | null;
|
|
161
173
|
readonly createdAt: string;
|
|
162
174
|
readonly createdBySubject: string;
|
|
163
175
|
readonly updatedAt: string;
|
|
@@ -435,7 +447,9 @@ export interface ManagedRequestStore {
|
|
|
435
447
|
}
|
|
436
448
|
export interface BusinessInformationStore extends SolutionInstallationStore, BusinessNoticeStore, BusinessGrantStore, ManagedRequestStore {
|
|
437
449
|
readonly pages: BusinessPageStore;
|
|
450
|
+
readonly nativeLifecycle: NativeRecordLifecycleStore;
|
|
451
|
+
readonly staff: BusinessStaffAuthority;
|
|
438
452
|
configurePrincipalAuthority(provider: BusinessPrincipalProvider | undefined): void;
|
|
439
|
-
listEligibleAssignees(scope: InstallationScope): Promise<readonly
|
|
453
|
+
listEligibleAssignees(scope: InstallationScope, actor: string): Promise<readonly EligibleBusinessAssignee[]>;
|
|
440
454
|
}
|
|
441
455
|
//# sourceMappingURL=contracts.d.ts.map
|
package/node_modules/@noodle-borg/service/dist/business-information/in-memory-business-pages.js
CHANGED
|
@@ -21,17 +21,15 @@ export class InMemoryBusinessPages {
|
|
|
21
21
|
const scope = validateScope(input.scope);
|
|
22
22
|
const actorSubject = validateScalar('page actor', input.actorSubject, 500);
|
|
23
23
|
const key = scopeKey(scope);
|
|
24
|
-
return this.locks.run(`grants:${key}`, () => this.locks.run(`intake:${key}`, async () => {
|
|
25
|
-
const grant = await this.business.getGrant(scope, actorSubject);
|
|
24
|
+
return this.business.staff.run(scope, actorSubject, 'installation:administer', () => this.locks.run(`grants:${key}`, () => this.locks.run(`intake:${key}`, async () => {
|
|
26
25
|
if (!(await this.business.getInstallation(scope)) ||
|
|
27
|
-
|
|
28
|
-
grant.revokedAt ||
|
|
26
|
+
!(await this.business.staff.allows(scope, actorSubject, 'installation:administer')) ||
|
|
29
27
|
!(await this.principals.allows(actorSubject)))
|
|
30
28
|
throw new BusinessPageError('business_page_forbidden');
|
|
31
29
|
const next = await nextBusinessPage(this.#records.get(key), { ...input, scope, actorSubject }, this.now().toISOString(), await this.business.getBusinessNotice(scope), assertReady);
|
|
32
30
|
this.#records.set(key, structuredClone(next));
|
|
33
31
|
return next;
|
|
34
|
-
}));
|
|
32
|
+
})), () => new BusinessPageError('business_page_forbidden'));
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
//# sourceMappingURL=in-memory-business-pages.js.map
|
|
@@ -10,10 +10,12 @@ export class InMemoryInstallationLifecycle {
|
|
|
10
10
|
#managedDefinition;
|
|
11
11
|
#application;
|
|
12
12
|
#notices = new Map();
|
|
13
|
-
#
|
|
13
|
+
#staff;
|
|
14
|
+
#principals;
|
|
14
15
|
constructor(input) {
|
|
15
16
|
this.#installations = input.installations;
|
|
16
|
-
this.#
|
|
17
|
+
this.#staff = input.staff;
|
|
18
|
+
this.#principals = input.principals;
|
|
17
19
|
this.#withLock = input.withLock;
|
|
18
20
|
this.#now = input.now;
|
|
19
21
|
this.#managedDefinition = input.managedDefinition;
|
|
@@ -35,9 +37,9 @@ export class InMemoryInstallationLifecycle {
|
|
|
35
37
|
async setBusinessNotice(input) {
|
|
36
38
|
const scope = validateScope(input.scope);
|
|
37
39
|
const notice = validatedNotice(input);
|
|
38
|
-
return this.#withLock(`grants:${scopeKey(scope)}`, async () => {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
return this.#staff.run(scope, input.actorSubject, 'installation:administer', () => this.#withLock(`grants:${scopeKey(scope)}`, async () => {
|
|
41
|
+
if (!(await this.#staff.allows(scope, input.actorSubject, 'installation:administer')) ||
|
|
42
|
+
!(await this.#principals.allows(input.actorSubject)))
|
|
41
43
|
throw new BusinessNoticeError('business_notice_forbidden');
|
|
42
44
|
const key = scopeKey(scope);
|
|
43
45
|
const current = this.#notices.get(key);
|
|
@@ -51,7 +53,7 @@ export class InMemoryInstallationLifecycle {
|
|
|
51
53
|
};
|
|
52
54
|
this.#notices.set(key, record);
|
|
53
55
|
return structuredClone(record);
|
|
54
|
-
});
|
|
56
|
+
}), () => new BusinessNoticeError('business_notice_forbidden'));
|
|
55
57
|
}
|
|
56
58
|
configureApplicationLifecycle(resolve) {
|
|
57
59
|
this.#application = resolve;
|
package/node_modules/@noodle-borg/service/dist/business-information/in-memory-native-lifecycle.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { checkLifecycleReview, lifecyclePreview, NativeLifecycleError, sameLifecycleReview, } from './native-record-lifecycle.js';
|
|
2
|
+
import { nativeCustodyBytes } from './native-storage-budget.js';
|
|
3
|
+
import { scopeKey } from './pagination.js';
|
|
4
|
+
import { validateScalar, validateScope } from './validation.js';
|
|
5
|
+
/** Local/test only. No await between inventory verification and the synchronous atomic commit. */
|
|
6
|
+
export class InMemoryNativeRecordLifecycle {
|
|
7
|
+
options;
|
|
8
|
+
#receipts = new Map();
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
preview(scope, actor) {
|
|
13
|
+
return this.run(scope, actor, (installation) => lifecyclePreview(installation, this.inventory(scope), this.options.now().toISOString()));
|
|
14
|
+
}
|
|
15
|
+
migrate(input) {
|
|
16
|
+
return this.run(input.scope, input.actor, (installation) => {
|
|
17
|
+
const key = scopeKey(input.scope);
|
|
18
|
+
const receipt = this.#receipts.get(key);
|
|
19
|
+
if (receipt &&
|
|
20
|
+
sameLifecycleReview(receipt.preview, input.preview) &&
|
|
21
|
+
receipt.actor === input.actor)
|
|
22
|
+
return { ...receipt.result, replayed: true };
|
|
23
|
+
const now = this.options.now().toISOString();
|
|
24
|
+
checkLifecycleReview(installation, this.inventory(input.scope), input.preview, now);
|
|
25
|
+
const updates = [];
|
|
26
|
+
let beforeBytes = 0, afterBytes = 0;
|
|
27
|
+
for (const [recordKey, record] of this.options.records) {
|
|
28
|
+
if (scopeKey(record.scope) !== key ||
|
|
29
|
+
record.deletedAt ||
|
|
30
|
+
record.retentionExpiresAt === null ||
|
|
31
|
+
record.retentionExpiresAt <= now)
|
|
32
|
+
continue;
|
|
33
|
+
const next = { ...record, retentionExpiresAt: null };
|
|
34
|
+
beforeBytes += nativeCustodyBytes(record);
|
|
35
|
+
afterBytes += nativeCustodyBytes(next);
|
|
36
|
+
updates.push([recordKey, next]);
|
|
37
|
+
}
|
|
38
|
+
const result = {
|
|
39
|
+
policy: 'explicit_erasure',
|
|
40
|
+
installationRevision: installation.revision + 1,
|
|
41
|
+
recordsPreserved: updates.length,
|
|
42
|
+
replayed: false,
|
|
43
|
+
};
|
|
44
|
+
this.options.budget.replace(key, beforeBytes, afterBytes);
|
|
45
|
+
for (const [id, record] of updates)
|
|
46
|
+
this.options.records.set(id, record);
|
|
47
|
+
this.options.installations.set(key, {
|
|
48
|
+
...installation,
|
|
49
|
+
nativeRecordLifecycle: 'explicit_erasure',
|
|
50
|
+
revision: result.installationRevision,
|
|
51
|
+
updatedAt: now,
|
|
52
|
+
updatedBySubject: input.actor,
|
|
53
|
+
});
|
|
54
|
+
this.#receipts.set(key, {
|
|
55
|
+
preview: structuredClone(input.preview),
|
|
56
|
+
actor: input.actor,
|
|
57
|
+
result,
|
|
58
|
+
});
|
|
59
|
+
return result;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
inventory(scope) {
|
|
63
|
+
return [...this.options.records.values()].filter((r) => scopeKey(r.scope) === scopeKey(scope) && !r.deletedAt);
|
|
64
|
+
}
|
|
65
|
+
run(input, actor, work) {
|
|
66
|
+
const scope = validateScope(input);
|
|
67
|
+
validateScalar('actor', actor, 256);
|
|
68
|
+
const { staff, locks, principals } = this.options;
|
|
69
|
+
return staff.run(scope, actor, 'installation:administer', () => locks.run(`grants:${scopeKey(scope)}`, () => locks.run(`intake:${scopeKey(scope)}`, async () => {
|
|
70
|
+
if (!(await staff.allows(scope, actor, 'installation:administer')) ||
|
|
71
|
+
!(await principals.allows(actor)))
|
|
72
|
+
throw new NativeLifecycleError('lifecycle_denied');
|
|
73
|
+
const installation = this.options.installations.get(scopeKey(scope));
|
|
74
|
+
if (!installation)
|
|
75
|
+
throw new NativeLifecycleError('lifecycle_unavailable');
|
|
76
|
+
return work(installation);
|
|
77
|
+
})), () => new NativeLifecycleError('lifecycle_denied'));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=in-memory-native-lifecycle.js.map
|
|
@@ -5,17 +5,22 @@ import { InMemoryBusinessPages } from './in-memory-business-pages.js';
|
|
|
5
5
|
import { InMemoryInstallationLifecycle, } from './in-memory-installation-lifecycle.js';
|
|
6
6
|
import { InMemoryBusinessInvitations } from './in-memory-invitations.js';
|
|
7
7
|
import { BusinessMemoryLocks } from './in-memory-locks.js';
|
|
8
|
+
import { InMemoryNativeRecordLifecycle } from './in-memory-native-lifecycle.js';
|
|
8
9
|
import { InstallationCapacityError, MAX_RETAINED_INSTALLATIONS_PER_ORG, } from './installation-capacity.js';
|
|
9
10
|
import { migrateLegacyRequestRecord } from './legacy-request-migration.js';
|
|
10
|
-
import {
|
|
11
|
+
import { eraseNativeMemoryRecord, expireNativeMemoryRecord } from './memory-native-retention.js';
|
|
12
|
+
import { activityFromRecord, applyRequestOperation, cloneGrant, cloneRecord, effectiveInstallation, idempotencyDigest, initialRecord, installationFingerprint, managedInstallationIntentMatches, normalizeInstallationInput, permissionsForBusinessRole, requestFingerprint, validateCollectionEnabled, validateExpectedRevision, validateStoredRecord, } from './model.js';
|
|
11
13
|
import { matchesNativeQueryMetadata, planNativeQuery, runNativeQuery } from './native-query.js';
|
|
12
14
|
import { commitNativeMemoryRecord, NativeStorageBudget } from './native-storage-budget.js';
|
|
13
15
|
import { afterCursor, compareRecords, decodeCursor, grantKey, installationIdKey, page, recordKey, scopeKey, } from './pagination.js';
|
|
14
16
|
import { BusinessPrincipalAuthority, } from './principal-authority.js';
|
|
17
|
+
import { BusinessStaffAuthority } from './staff-authority.js';
|
|
15
18
|
import { boundedExportPageSize, boundedPageSize, validateEmail, validateScalar, validateScope, } from './validation.js';
|
|
16
19
|
/** Process-local development/test adapter. Hosted services must use the PostgreSQL adapter. */
|
|
17
20
|
export class InMemoryBusinessInformationStore {
|
|
21
|
+
staff = new BusinessStaffAuthority((scope, subject) => this.getGrant(scope, subject));
|
|
18
22
|
pages;
|
|
23
|
+
nativeLifecycle;
|
|
19
24
|
#installations = new Map();
|
|
20
25
|
#installationIds = new Map();
|
|
21
26
|
#publicIds = new Map();
|
|
@@ -39,9 +44,19 @@ export class InMemoryBusinessInformationStore {
|
|
|
39
44
|
this.#publicId = options.publicId ?? (() => `sol_${randomUUID().replaceAll('-', '')}`);
|
|
40
45
|
this.#managedDefinition = options.managedDefinition;
|
|
41
46
|
this.pages = new InMemoryBusinessPages(this, this.#locks, this.#principals, this.#now);
|
|
47
|
+
this.nativeLifecycle = new InMemoryNativeRecordLifecycle({
|
|
48
|
+
installations: this.#installations,
|
|
49
|
+
records: this.#records,
|
|
50
|
+
locks: this.#locks,
|
|
51
|
+
staff: this.staff,
|
|
52
|
+
principals: this.#principals,
|
|
53
|
+
budget: this.#custody,
|
|
54
|
+
now: this.#now,
|
|
55
|
+
});
|
|
42
56
|
this.#lifecycle = new InMemoryInstallationLifecycle({
|
|
43
57
|
installations: this.#installations,
|
|
44
|
-
|
|
58
|
+
staff: this.staff,
|
|
59
|
+
principals: this.#principals,
|
|
45
60
|
withLock: (key, operation) => this.#locks.run(key, operation),
|
|
46
61
|
now: this.#now,
|
|
47
62
|
managedDefinition: this.#managedDefinition,
|
|
@@ -58,8 +73,8 @@ export class InMemoryBusinessInformationStore {
|
|
|
58
73
|
configurePrincipalAuthority(provider) {
|
|
59
74
|
this.#principals.configure(provider);
|
|
60
75
|
}
|
|
61
|
-
async listEligibleAssignees(scope) {
|
|
62
|
-
return this.#principals.eligible(await this.listGrants(scope));
|
|
76
|
+
async listEligibleAssignees(scope, actor) {
|
|
77
|
+
return this.staff.eligibleAssignees(scope, actor, async () => this.#principals.eligible(await this.listGrants(scope)));
|
|
63
78
|
}
|
|
64
79
|
getBusinessNotice(scope) {
|
|
65
80
|
return this.#lifecycle.getBusinessNotice(scope);
|
|
@@ -400,11 +415,8 @@ export class InMemoryBusinessInformationStore {
|
|
|
400
415
|
return { ok: false, reason: 'invalid_transition', currentRevision: current.revision };
|
|
401
416
|
}
|
|
402
417
|
if (input.operation.kind === 'assign' && input.operation.assigneeSubject !== undefined) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
assignee.revokedAt !== undefined ||
|
|
406
|
-
assignee.role === 'viewer' ||
|
|
407
|
-
!(await this.#principals.allows(assignee.subject))) {
|
|
418
|
+
if (!(await this.staff.allows(input.scope, input.operation.assigneeSubject, 'records:assign')) ||
|
|
419
|
+
!(await this.#principals.allows(input.operation.assigneeSubject))) {
|
|
408
420
|
return { ok: false, reason: 'invalid_assignee', currentRevision: current.revision };
|
|
409
421
|
}
|
|
410
422
|
}
|
|
@@ -495,6 +507,7 @@ export class InMemoryBusinessInformationStore {
|
|
|
495
507
|
const candidates = [...this.#records.entries()]
|
|
496
508
|
.filter(([key, record]) => (scope === undefined || key.startsWith(`${scope}\0`)) &&
|
|
497
509
|
record.deletedAt === undefined &&
|
|
510
|
+
record.retentionExpiresAt !== null &&
|
|
498
511
|
record.retentionExpiresAt <= now.toISOString())
|
|
499
512
|
.slice(0, limit);
|
|
500
513
|
let purged = 0;
|
|
@@ -510,34 +523,17 @@ export class InMemoryBusinessInformationStore {
|
|
|
510
523
|
const now = this.#now().toISOString();
|
|
511
524
|
return [...this.#records.entries()]
|
|
512
525
|
.filter(([key, record]) => key.startsWith(prefix) &&
|
|
513
|
-
(record.deletedAt !== undefined ||
|
|
526
|
+
(record.deletedAt !== undefined ||
|
|
527
|
+
record.retentionExpiresAt === null ||
|
|
528
|
+
record.retentionExpiresAt > now))
|
|
514
529
|
.map(([, record]) => record)
|
|
515
530
|
.sort(compareRecords);
|
|
516
531
|
}
|
|
517
532
|
#expireRecordIfNeeded(key, now) {
|
|
518
|
-
|
|
519
|
-
if (current === undefined ||
|
|
520
|
-
current.deletedAt !== undefined ||
|
|
521
|
-
current.retentionExpiresAt > now.toISOString()) {
|
|
522
|
-
return current;
|
|
523
|
-
}
|
|
524
|
-
const erased = deletedRecord(current, 'system:retention', now, 'retention_expired');
|
|
525
|
-
this.#commitRecord(key, erased, 'retention_expired');
|
|
526
|
-
return erased;
|
|
533
|
+
return expireNativeMemoryRecord({ records: this.#records, commit: this.#commitRecord.bind(this) }, key, now);
|
|
527
534
|
}
|
|
528
535
|
#eraseRecord(key, expectedRevision, actorSubject, reason) {
|
|
529
|
-
|
|
530
|
-
? this.#records.get(key)
|
|
531
|
-
: this.#expireRecordIfNeeded(key, this.#now());
|
|
532
|
-
if (current === undefined || current.deletedAt !== undefined) {
|
|
533
|
-
return { ok: false, reason: 'not_found', currentRevision: current?.revision ?? 0 };
|
|
534
|
-
}
|
|
535
|
-
if (current.revision !== expectedRevision) {
|
|
536
|
-
return { ok: false, reason: 'conflict', currentRevision: current.revision };
|
|
537
|
-
}
|
|
538
|
-
const erased = deletedRecord(current, actorSubject, this.#now(), reason);
|
|
539
|
-
this.#commitRecord(key, erased, reason === 'customer_request' ? 'deleted' : 'retention_expired');
|
|
540
|
-
return { ok: true, record: cloneRecord(erased) };
|
|
536
|
+
return eraseNativeMemoryRecord({ records: this.#records, commit: this.#commitRecord.bind(this) }, key, expectedRevision, actorSubject, reason, this.#now());
|
|
541
537
|
}
|
|
542
538
|
#commitRecord(key, record, kind) {
|
|
543
539
|
commitNativeMemoryRecord(this.#records, this.#activities, this.#custody, key, record, activityFromRecord(record, kind));
|