axstack 0.56.0 → 0.57.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/package.json +1 -1
- package/skills/axstack/references/diligence.md +3 -1
- package/skills/axstack/references/fleet-dispatch.md +85 -8
- package/skills/axstack/references/fleet-route-schema.md +16 -1
- package/skills/axstack/references/lifecycle.md +6 -0
- package/skills/axstack/references/local-artifacts.md +100 -0
- package/skills/axstack/references/role-roster.md +5 -0
- package/skills/axstack/references/t3-runtime.md +7 -0
- package/skills/axstack/references/ui-verification.md +4 -1
- package/skills/axstack/scripts/dispatch-plan.js +11 -1
- package/skills/axstack/scripts/fleet-config.js +3 -1
- package/skills/axstack/scripts/fleet-destination.js +57 -9
- package/skills/axstack/scripts/fleet-dispatch.js +10 -2
- package/skills/axstack/scripts/fleet-facts.js +6 -1
- package/skills/axstack/scripts/fleet-native-receipt.js +35 -0
- package/skills/axstack/scripts/fleet-receipt.js +63 -12
- package/skills/axstack/scripts/fleet-scope.js +24 -0
- package/skills/axstack-explain/SKILL.md +4 -0
- package/skills/axstack-explain/references/visual-qa.md +6 -1
- package/skills/axstack-publish/SKILL.md +4 -0
- package/skills/axstack-research/SKILL.md +4 -0
- package/skills/axstack-review/SKILL.md +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0",
|
|
4
4
|
"description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks T3 Code capabilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -5,7 +5,9 @@ and bounded follow-ups are defined in [Peer mode](../../axstack-review/SKILL.md#
|
|
|
5
5
|
|
|
6
6
|
Read the [T3 runtime boundary](t3-runtime.md) before dispatch.
|
|
7
7
|
Dispatch `axstack-diligence` with async `delegate_task` in the driver worktree,
|
|
8
|
-
using a pinned brief and private evidence paths.
|
|
8
|
+
using a pinned brief and private evidence paths. Explicit non-repository
|
|
9
|
+
research/article diligence uses [Local artifacts](local-artifacts.md) with
|
|
10
|
+
local shared isolation and independently reopened sources, without a fake Git repo.
|
|
9
11
|
It is read-only, never authors or edits, and returns `PASS`, `FINDINGS`, or `UNKNOWN`
|
|
10
12
|
with locations, observed evidence, and limits. A stale or missing receipt is
|
|
11
13
|
not a pass. Keep its first pass independent of other reviewers and workers.
|
|
@@ -7,6 +7,12 @@ records and forge actions. Helpers validate explicit saved inputs only: no
|
|
|
7
7
|
network, clock, dispatch, retry, merge, retained state or aggregate permission grant.
|
|
8
8
|
Synthetic evidence can pass binding checks; helpers cannot authenticate it.
|
|
9
9
|
|
|
10
|
+
For explicit local non-repository Research/Explain/Publish work, apply
|
|
11
|
+
[Local artifacts](local-artifacts.md): its discriminated scope, native read/list
|
|
12
|
+
null-identity proof and owned folders replace only the repository/checkout terms.
|
|
13
|
+
All native execution authority and independent exact-byte review remain required.
|
|
14
|
+
Engineering/code review and remote routes retain their existing rules.
|
|
15
|
+
|
|
10
16
|
## Observe before selecting
|
|
11
17
|
|
|
12
18
|
1. Record installed Axstack and both T3 versions, directed origin/target link,
|
|
@@ -25,7 +31,7 @@ Synthetic evidence can pass binding checks; helpers cannot authenticate it.
|
|
|
25
31
|
3. Keep the model/effort/provider pins from the run; do not re-resolve to another
|
|
26
32
|
model when quota/auth/timeout fails. Missing native `t3_thread_configuration`
|
|
27
33
|
means unavailable effective fields stay unknown. Catalog runtimeModes advertise
|
|
28
|
-
|
|
34
|
+
provider restrictions; they do not prove effective access. Native task/thread facts supply
|
|
29
35
|
only the fields actually returned. A worker assertion is not native readback.
|
|
30
36
|
4. Normalize current driver assignments and the pending batch as below. Sample
|
|
31
37
|
telemetry with C2's bounded command, then recompute C1 immediately before each
|
|
@@ -70,8 +76,28 @@ read envelope is `{tool,environmentId,args,observedAt,evidence,value}` where arg
|
|
|
70
76
|
excludes the forwarding environmentId, and evidence points to the preserved raw
|
|
71
77
|
native call/result. environment/project/capabilities are read on the selected
|
|
72
78
|
host; links are read on origin. The capabilities envelope uses `valueText` for
|
|
73
|
-
exact raw catalog bytes instead of value.
|
|
74
|
-
must match the requested repositoryId.
|
|
79
|
+
exact raw catalog bytes instead of value. For repository scope, project repositoryIdentity.canonicalKey
|
|
80
|
+
must match the requested repositoryId. If project-read identity is null/omitted,
|
|
81
|
+
`projectList` may carry an ordered array of `t3_project_list` read envelopes on
|
|
82
|
+
that same environment. Collect from cursor 0 through native nextCursor:null,
|
|
83
|
+
recording actual cursor/limit arguments (native defaults: 0/20, limit at most 100).
|
|
84
|
+
Every page must join without gaps, extra arguments or duplicate project IDs.
|
|
85
|
+
The selected project's ID and workspaceRoot must equal project-read; every
|
|
86
|
+
available canonicalKey must equal repositoryId, including when both reads have
|
|
87
|
+
identity. In repository scope, missing identity in both holds; local-artifact
|
|
88
|
+
scope instead requires explicit null in both reads. Incomplete pagination and
|
|
89
|
+
any conflict hold for either scope.
|
|
90
|
+
|
|
91
|
+
For observed T3 0.1.5 native Codex/Claude drivers, omitted provider runtimeModes
|
|
92
|
+
means no provider restriction: native resolveRuntimeMode and RuntimePolicy
|
|
93
|
+
preserve the requested mode. Bind this only with catalog runtimeMode equal to
|
|
94
|
+
the request, exactly one selected provider, canRunChildTask:true and an explicit
|
|
95
|
+
empty constraints array. Explicit mode restrictions must include the requested
|
|
96
|
+
mode. Unknown version/driver omission semantics, missing caller authority,
|
|
97
|
+
unavailable providers or restrictions hold. This is preflight support, never
|
|
98
|
+
proof of a child's effective mode; terminal receipts still require actual native
|
|
99
|
+
readback. Preserve the exact catalog bytes/digest; do not synthesize runtimeModes
|
|
100
|
+
or repositoryIdentity in the saved raw reads. Unknown mappings hold rather than guessing.
|
|
75
101
|
The pure `inspectDestination` API returns per-term pass/fail/unknown with evidence;
|
|
76
102
|
`dispatch-plan` holds on a failed/unknown term. This is supplied binding proof only.
|
|
77
103
|
|
|
@@ -80,9 +106,11 @@ recommendation, explicit evaluation time and config pin. Compare its target to t
|
|
|
80
106
|
independently selected host/environment/project/model/options/runtimeMode. Copy only
|
|
81
107
|
native supported fields, never inputs or fleetBinding. Call `delegate_task` with
|
|
82
108
|
that exact target, full-access, a role-labelled title, stable clientRequestId and
|
|
83
|
-
scope.
|
|
84
|
-
no shell-created child checkout.
|
|
85
|
-
|
|
109
|
+
scope. Code reviewers require native isolation:"worktree" and pinned candidate/base;
|
|
110
|
+
no shell-created child checkout. Explicit local artifact reviewers instead use
|
|
111
|
+
shared isolation and SHA-256 snapshots under [Local artifacts](local-artifacts.md);
|
|
112
|
+
artifact writers use only their owned artifact/evidence folders. Other nonwriters
|
|
113
|
+
write only their named private evidence directory on that host. Preserve requested arguments and native response.
|
|
86
114
|
|
|
87
115
|
Record taskId on the origin plus remoteChild.environmentId/threadId. The native
|
|
88
116
|
remoteChild schema need not return runId: normalize it from observed task/thread
|
|
@@ -99,7 +127,9 @@ recommendation at the same model/permission pins; record the exclusion evidence.
|
|
|
99
127
|
|
|
100
128
|
Preserve schemaVersion:1, recommendationJSON (exact bytes), configDigest,
|
|
101
129
|
attempt (key, exact title, clientRequestId, origin environmentId/threadId/taskId,
|
|
102
|
-
candidate/base SHAs
|
|
130
|
+
candidate/base SHAs for repositories, artifact SHA-256/null base for local artifacts),
|
|
131
|
+
requested target/runtimeMode/isolation and native submission. Artifact ownership
|
|
132
|
+
and byte readbacks follow [Local artifacts](local-artifacts.md#receipts).
|
|
103
133
|
The driver's independent fleetAttempt is the expected run-record identity;
|
|
104
134
|
never copy it from an arriving receipt to make a comparison pass.
|
|
105
135
|
|
|
@@ -119,6 +149,9 @@ submission.childRunId; never use lexical ordering of IDs or the original summary
|
|
|
119
149
|
Require completed/result_available/no pending children/no unanswered questions or
|
|
120
150
|
approval. Require exactly one AXSTACK-DONE key/candidate/report marker on both origin's
|
|
121
151
|
latestTerminalSummary and the destination's final current-run agent message.
|
|
152
|
+
For local artifacts only, T3 0.1.5's deduplicated summary uses the exact
|
|
153
|
+
run/byte-bound exception in [Local artifacts](local-artifacts.md#receipts);
|
|
154
|
+
never rewrite raw native fields or extend it to remote/engineering receipts.
|
|
122
155
|
A completion marker may be followed by the T3
|
|
123
156
|
`PR <url>, head <sha>, checks and reviews <state>` trailer. Other trailing text
|
|
124
157
|
or additional AXSTACK markers keep the receipt held.
|
|
@@ -131,7 +164,51 @@ For isolated review include checkout with those fields, candidate/base and readb
|
|
|
131
164
|
Never infer local file existence from a remote absolute path.
|
|
132
165
|
Requested settings remain separate from effective native status.providerInstanceId,
|
|
133
166
|
model/options and thread.providerInstanceId/model/runtimeMode. Missing effective
|
|
134
|
-
options or mode is unknown and holds required verification
|
|
167
|
+
options or mode is unknown and holds required verification, except for the bounded
|
|
168
|
+
T3 0.1.5 initial remote-launch join below.
|
|
169
|
+
|
|
170
|
+
### T3 0.1.5 initial remote receipt
|
|
171
|
+
|
|
172
|
+
The remote `readRemoteTask` emitter omits options and returns null childRunId and
|
|
173
|
+
latestTerminalRunId. Preserve those raw omissions/nulls. A current family shell
|
|
174
|
+
is current thread metadata, not an immutable settings record for an older run.
|
|
175
|
+
Never use it to authorize a continuation from an origin summary lacking a run ID.
|
|
176
|
+
|
|
177
|
+
For the sole initial remote run, optional receipt `nativeFamily` can corroborate
|
|
178
|
+
settings and run identity. The driver acquires `orchestration.getThreadFamily`
|
|
179
|
+
with exact `{threadId}` on the actual destination using an authenticated native
|
|
180
|
+
RPC read with `orchestration:read` scope. This is metadata access, not agent
|
|
181
|
+
execution or an alternate worker launch path. Record host/environment identity,
|
|
182
|
+
authenticated acquisition provenance, exact request/result, and independently
|
|
183
|
+
observed server version. Do not execute an agent through SSH/CLI to obtain it.
|
|
184
|
+
Temporary read sessions require their authorized lifecycle and revocation; never
|
|
185
|
+
put tokens/tickets into evidence. This contract grants no auth/session authority.
|
|
186
|
+
|
|
187
|
+
The envelope is `{tool:"orchestration.getThreadFamily",transport:"t3-native-rpc",
|
|
188
|
+
environmentId,serverVersion:"0.1.5",args:{threadId},observedAt,evidence,value}`.
|
|
189
|
+
`value` is the unmodified native `{snapshotSequence,threads}` response. Retain
|
|
190
|
+
raw bytes separately, including any diagnostic/revocation trailer; parse only
|
|
191
|
+
the JSON response into a separate file. Never rewrite the raw response as JSON.
|
|
192
|
+
The version must also match selected destination versions; observe the family
|
|
193
|
+
after thread/wait and no later than receipt evaluation. Missing provenance holds.
|
|
194
|
+
|
|
195
|
+
Require a unique child with the selected project, exact title/current run,
|
|
196
|
+
provider/model/options/mode and delegatedFrom origin environment/thread. Every
|
|
197
|
+
returned family member must explicitly have idle/completed status, null active
|
|
198
|
+
run/request, zero pending guest input and an empty pendingBackgroundTasks array.
|
|
199
|
+
Unknown or live descendants hold even when remote status claims no pending child.
|
|
200
|
+
|
|
201
|
+
The fallback requires native thread runCount:1, exactly one recentRuns row at
|
|
202
|
+
ordinal:1, null local child IDs/childRunIds on status/submission, null origin
|
|
203
|
+
latestTerminalRunId, and identical origin/destination final text. Existing
|
|
204
|
+
current-run, completed terminal, marker, question and host/path checks still
|
|
205
|
+
apply. Explicit status options or run IDs cannot be overwritten by family facts.
|
|
206
|
+
T3 remote launch forwards the accepted explicit model selection and uses it for
|
|
207
|
+
both thread creation and the initial message; this is the source-backed scope
|
|
208
|
+
of the join. More runs or missing counts/ordinals hold. Later continuations need
|
|
209
|
+
actual result-run/per-run settings provenance; repeated matching markers or
|
|
210
|
+
current family settings are insufficient. Complete native statuses retain their
|
|
211
|
+
existing run-ID path. Saved inputs cannot authenticate their own provenance.
|
|
135
212
|
|
|
136
213
|
`inspectFleetReceipt(receipt,{attempt,recommendation,recommendationJSON,now})`
|
|
137
214
|
returns terms, never authority. Existing dispatch-plan --verify can additionally
|
|
@@ -112,6 +112,13 @@ for serial emulator use, desktop safety and declared thermal prechecks.
|
|
|
112
112
|
| `pendingAssignments` | Array of `{clientRequestId, environmentId, taskId?}` |
|
|
113
113
|
| `exclusions` | `{hosts: {hostId: reason}, routes: {routeId: reason}}`; nonempty reason strings |
|
|
114
114
|
|
|
115
|
+
`request.scope` may be `{kind:"repository"}` with a nonempty repositoryId;
|
|
116
|
+
legacy omitted scope retains that repository requirement. The explicit
|
|
117
|
+
`{kind:"local-artifact", ...}` alternative requires repositoryId:null and the
|
|
118
|
+
workflow, role, native project and ownership fields in [Local artifacts](local-artifacts.md).
|
|
119
|
+
Artifact destination project also carries workspaceRoot; only the bound local
|
|
120
|
+
environment/project/workspace is eligible. Null alone never selects a route.
|
|
121
|
+
|
|
115
122
|
`role` is `reader` (all nonwriter roles) or `author`. `requiredCapabilities`
|
|
116
123
|
is an array of strings. An authentication requirement names the actual session
|
|
117
124
|
capability; only the host proving that session is eligible. No credential
|
|
@@ -168,6 +175,11 @@ Optional `nativeInventory` is normalized before counts under
|
|
|
168
175
|
Unknown native totals are null; balance ranking uses the separately labelled
|
|
169
176
|
pendingLowerBound. Counts also retain inventoryReasons/evidence; flags.countsUnknown follows normalized status.
|
|
170
177
|
Optional `nativeDestination` carries observed native forwarded preflight reads.
|
|
178
|
+
Optional `nativeDestination.projectList` is the complete ordered native project-list
|
|
179
|
+
page envelopes used to join a cold project-read repository identity; preserve
|
|
180
|
+
raw reads and catalogs. Conflicting identities/workspaces or incomplete pages hold.
|
|
181
|
+
T3 0.1.5 native provider mode omissions require the documented caller/provider
|
|
182
|
+
authority evidence, never synthesized catalog fields.
|
|
171
183
|
The same reference defines terminal receipt validation and its supplied-evidence
|
|
172
184
|
limitation. Live drivers require these observed facts, not synthetic caller claims.
|
|
173
185
|
Legacy normalized input remains unchanged. Remote coordinator remains disabled.
|
|
@@ -306,7 +318,10 @@ unavailable destination model/options, or role/settings/config disagreement.
|
|
|
306
318
|
It never reinterprets a requested model pin as a different available model.
|
|
307
319
|
|
|
308
320
|
Task plans add `environmentId`/`projectId` to `target`; launch plans add them
|
|
309
|
-
beside `modelSelection`. All requested options survive as T3 option entries.
|
|
321
|
+
beside `modelSelection`. All requested options survive as T3 option entries. Explicit local-artifact
|
|
322
|
+
plans require nativeDestination, the exact scope role and task kind; they emit
|
|
323
|
+
isolation:"shared" and artifactScope evidence. The latter is never a native
|
|
324
|
+
argument. Artifact readback compares scope and shared isolation as well.
|
|
310
325
|
Both include `fleetBinding: {configDigest, destinationSnapshotId,
|
|
311
326
|
destinationCapabilitiesDigest}`. This is preserved evidence, not a T3 launch
|
|
312
327
|
parameter. Copy only tool-supported launch fields. Coordinator author routes
|
|
@@ -29,6 +29,12 @@ Prefer parallel independent bounded work; no redundant workers. Fanout is depend
|
|
|
29
29
|
capacity-driven within host/spending limits. [PR shape](pr-shape.md) covers
|
|
30
30
|
theme/size; queue dependencies under [PR chains](pr-chain.md).
|
|
31
31
|
|
|
32
|
+
For explicit non-repository Research/Explain/Publish artifacts, use
|
|
33
|
+
[Local artifacts](local-artifacts.md) for owned shared delegates and SHA-256
|
|
34
|
+
receipts. The reviewer checkout row above applies to code candidates; article
|
|
35
|
+
review retains independent source/fact and exact-byte UI/design checks.
|
|
36
|
+
Null identity does not waive engineering isolation or authorize remote artifacts.
|
|
37
|
+
|
|
32
38
|
## Ownership
|
|
33
39
|
|
|
34
40
|
The PR owner is accountable for candidate, fixes, evidence, monitoring.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Local non-repository artifacts
|
|
2
|
+
|
|
3
|
+
Research, Explain and Publish may use an explicitly bound non-repository
|
|
4
|
+
artifact scope. Null repository identity alone never authorizes a route.
|
|
5
|
+
Engineering authors and code reviewers still require a repository and native
|
|
6
|
+
Git worktree isolation. Remote artifacts and remote author/coordinator work
|
|
7
|
+
remain outside this scope.
|
|
8
|
+
|
|
9
|
+
## Scope and ownership
|
|
10
|
+
|
|
11
|
+
Use `request.scope` below with `repositoryId:null` and `role:reader`.
|
|
12
|
+
Here reader means no product-code authority; an explainer may write only its
|
|
13
|
+
owned article artifacts. `task` remains a load preference, not role authority.
|
|
14
|
+
The scope is evidence for the helpers and brief, never a native T3 argument.
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"kind": "local-artifact",
|
|
19
|
+
"workflow": "explain",
|
|
20
|
+
"roleId": "axstack-explainer",
|
|
21
|
+
"environmentId": "<current environment>",
|
|
22
|
+
"projectId": "<actual parent project>",
|
|
23
|
+
"workspaceRoot": "/scratch",
|
|
24
|
+
"artifactRoot": "/scratch/run/artifacts/author-a1",
|
|
25
|
+
"evidenceRoot": "/scratch/run/evidence/author-a1",
|
|
26
|
+
"reservedRoots": ["/scratch/run/artifacts/other-attempt"]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`workflow` is research, explain or publish. All three permit the configured
|
|
31
|
+
research requirements/code/code-sol/web/web-sol/X and explore-execution/
|
|
32
|
+
explore-execution-sol source roles plus diligence. Explain and Publish also
|
|
33
|
+
permit explainer, explainer-review and the configured UI verifier roles.
|
|
34
|
+
Use their full `axstack-` role IDs; this list does not make an absent seat
|
|
35
|
+
available or change provider/model/effort/options/runtime authority.
|
|
36
|
+
The selected role ID must equal scope.roleId in dispatch-plan.
|
|
37
|
+
No engineering author, code reviewer, owner or arbitrary role can use this scope.
|
|
38
|
+
|
|
39
|
+
The driver binds the native parent project and its real workspaceRoot. Require
|
|
40
|
+
explicit null repositoryIdentity in both project-read and complete project-list
|
|
41
|
+
reads; omitted, conflicting or incomplete observations hold. Native environment,
|
|
42
|
+
project, catalog, provider restrictions, options, runtime and access checks still
|
|
43
|
+
apply. Use only a local route in that environment/project/workspace. Do not
|
|
44
|
+
fabricate a repository ID, initialize Git or attach an unrelated repository.
|
|
45
|
+
|
|
46
|
+
Give every attempt separate owned artifact and evidence directories, strict
|
|
47
|
+
children of the observed workspaceRoot. They must be canonical absolute paths,
|
|
48
|
+
with no traversal, shared-root writes or ancestor/equal overlap. `reservedRoots`
|
|
49
|
+
is the driver's current list of other attempts' owned artifact/evidence roots;
|
|
50
|
+
include pending and retained attempts, never guess an empty inventory.
|
|
51
|
+
The helper checks lexical paths and overlaps; the driver must independently
|
|
52
|
+
verify real paths, no symlink escapes, ownership and exclusive writers on disk.
|
|
53
|
+
Unknown ownership holds. This is supplied evidence, not an authenticated lock.
|
|
54
|
+
|
|
55
|
+
## Native dispatch and review
|
|
56
|
+
|
|
57
|
+
Use async native `delegate_task` with `isolation:"shared"`, exact local target,
|
|
58
|
+
role-labelled title and stable clientRequestId. Native local projectId must equal
|
|
59
|
+
the parent's project; moving to another local project is unsupported. Preserve
|
|
60
|
+
requested arguments and native effective settings separately. Dispatch-plan
|
|
61
|
+
requires nativeDestination and `--kind task`; it returns artifactScope as evidence
|
|
62
|
+
and shared isolation. Copy only supported native arguments into delegate_task.
|
|
63
|
+
No top-level launch, shell checkout, live policy edit or per-run bypass is needed.
|
|
64
|
+
|
|
65
|
+
Research branches keep independent first passes and cited-source reopening.
|
|
66
|
+
Diligence reopens answer-changing claims; it never becomes the article author.
|
|
67
|
+
The explainer remains sole source writer. Reviewers/verifiers get their own
|
|
68
|
+
byte-identical snapshot in their owned artifact directory; their reports/probes
|
|
69
|
+
stay in their separate evidence directory and never edit the author's original.
|
|
70
|
+
Independent explainer review checks the exact SHA-256 bytes. Before public
|
|
71
|
+
publication require Playwright layout/interaction evidence and independent
|
|
72
|
+
source/fact and design review of those same bytes under Explain/Publish rules.
|
|
73
|
+
Changed bytes invalidate affected reviews and renders; unknown proof holds.
|
|
74
|
+
Shared filesystem access does not permit cross-reading independent first passes.
|
|
75
|
+
Publication remains the explicitly authorized driver's action.
|
|
76
|
+
|
|
77
|
+
## Receipts
|
|
78
|
+
|
|
79
|
+
Retain the normal native task/thread/run, pending-question and settings checks.
|
|
80
|
+
For artifact receipts, `attempt.candidate` and marker `head` are the exact
|
|
81
|
+
64-hex artifact SHA-256; `attempt.base` is null. They are not Git revisions.
|
|
82
|
+
Bind `artifactScope` to the requested scope and report to evidenceRoot.
|
|
83
|
+
Supply `artifact:{path,sha256,readback:{path,sha256,evidence}}` for the independently
|
|
84
|
+
hashed owned snapshot. Requested isolation must be shared. Native child worktreePath may be null;
|
|
85
|
+
on T3 0.1.5 it may also inherit the parent's non-Git scratch path. For the latter,
|
|
86
|
+
include `parent` as a saved `t3_thread_read` observation (tool, environmentId,
|
|
87
|
+
args.threadId, observedAt, evidence, value.thread). Bind it to the attempt's
|
|
88
|
+
origin thread, selected project, child parentThreadId/subagent relationship and
|
|
89
|
+
identical canonical path inside workspaceRoot. Verify the actual filesystem;
|
|
90
|
+
this is shared access, not a Git checkout or OS isolation. Checkout metadata
|
|
91
|
+
remains forbidden. Unknown or different parent/path evidence holds.
|
|
92
|
+
|
|
93
|
+
T3 0.1.5 deduplicates local task results: latestTerminalSummary can be null when
|
|
94
|
+
summary contains the same result. For local artifact receipts only, use that
|
|
95
|
+
summary when childRunId and latestTerminalRunId both equal the current completed
|
|
96
|
+
run and its bytes equal the destination final message. An explicit conflicting
|
|
97
|
+
terminal summary, stale run, other version or remote result does not use this
|
|
98
|
+
fallback. Preserve the raw native fields; do not rewrite them. Never synthesize an effective field from requested settings.
|
|
99
|
+
The driver checks actual bytes and readbacks; helpers validate saved data only.
|
|
100
|
+
Keep artifact receipts separate from Git candidate publication/PR/merge receipts.
|
|
@@ -6,6 +6,11 @@ native destination. Remote reviewers require T3 worktree isolation; no shell chi
|
|
|
6
6
|
checkout. Host names confer no role or permission. Remote authors/coordinator stay
|
|
7
7
|
disabled; local isolated authors remain supported even overloaded.
|
|
8
8
|
|
|
9
|
+
Explicit non-repository Research/Explain/Publish uses the bounded role allowlist
|
|
10
|
+
and local shared-artifact isolation in [Local artifacts](local-artifacts.md).
|
|
11
|
+
This changes no configured seat, source independence or exact-byte review duty.
|
|
12
|
+
Engineering authors and code reviewers cannot acquire this artifact authority.
|
|
13
|
+
|
|
9
14
|
- Chat drives (no role ID); `axstack-owner` owns one PR and
|
|
10
15
|
`axstack-author` its sole writer.
|
|
11
16
|
For own PRs, automatic merge is the default under the
|
|
@@ -204,6 +204,13 @@ a candidate follows [UI verification](ui-verification.md).
|
|
|
204
204
|
|
|
205
205
|
The driver must be the sole run-record writer and enforce one writer per
|
|
206
206
|
candidate; it never writes tracked candidate source or tests or repairs an author's source.
|
|
207
|
+
For explicit non-repository Research/Explain/Publish only,
|
|
208
|
+
[Local artifacts](local-artifacts.md) supplies the artifact branch of this table:
|
|
209
|
+
source readers, diligence, explainers and article/UI reviewers use local shared
|
|
210
|
+
native delegates with separate owned folders and exact SHA-256 snapshots.
|
|
211
|
+
Its role allowlist excludes engineering authors and code reviewers; their native
|
|
212
|
+
Git worktree rules above remain mandatory. Null repository alone grants nothing.
|
|
213
|
+
|
|
207
214
|
The driver may write planning artifacts (spec, ticket map) in its own worktree when the selected store is repository Markdown.
|
|
208
215
|
Repairs return to that author. Missing or idle sessions grant no ownership transfer.
|
|
209
216
|
The current chat/driver has no role row in any preset.
|
|
@@ -24,8 +24,11 @@ All other browser checks outside the two named exceptions remain delegated to co
|
|
|
24
24
|
|
|
25
25
|
Read the [T3 runtime boundary](t3-runtime.md) before dispatch; for candidate
|
|
26
26
|
checks use its driver-made disposable detached checkout at the pinned candidate SHA.
|
|
27
|
+
For explicit non-repository article checks, use the local shared delegate and
|
|
28
|
+
owned SHA-256 snapshot in [Local artifacts](local-artifacts.md); no Git candidate
|
|
29
|
+
is fabricated. Engineering candidate checks retain the checkout requirement.
|
|
27
30
|
Use the configured verifier browser and honor the user browser-tool policy.
|
|
28
|
-
|
|
31
|
+
Engineering candidate browser and visual checks must run in a delegated UI verifier role in its own detached checkout; outputs go to its private evidence folder, never the driver worktree.
|
|
29
32
|
|
|
30
33
|
For every interactive page, require a real-browser `axstack-ui-verifier` interaction pass.
|
|
31
34
|
Give the evidence path and SHA-256 of the exact bytes in the verifier brief.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { inspectFleetReceipt } from './fleet-receipt.js';
|
|
3
3
|
import { bindFleet } from './fleet-dispatch.js';
|
|
4
|
+
import { canonical } from './fleet-config.js';
|
|
4
5
|
|
|
5
6
|
const fleetFlags = ['--recommendation', '--fleet-input', '--now', '--config-digest'];
|
|
6
7
|
const flags = ['--role', '--roles', '--capabilities', '--kind', '--verify', '--out', ...fleetFlags];
|
|
@@ -34,6 +35,9 @@ by its destination facts. It is never the origin catalog by default.
|
|
|
34
35
|
Fleet verification requires environmentId/projectId and fleetBinding alongside
|
|
35
36
|
the effective T3 configuration. C3 collects that host-qualified evidence envelope.
|
|
36
37
|
Optional input.nativeDestination validates saved native forwarded reads.
|
|
38
|
+
Explicit local-artifact scope requires those reads and kind task; emits isolation:"shared".
|
|
39
|
+
artifactScope is preserved evidence only, never a native tool argument.
|
|
40
|
+
Artifact verification also requires artifactScope and independently checked isolation.
|
|
37
41
|
Optional --verify fleetAttempt/fleetReceipt validates host-qualified terminal facts;
|
|
38
42
|
--out retains per-term receipt evidence. Synthetic bindings cannot authenticate reads.
|
|
39
43
|
Coordinator routes remain unsupported here. A plan grants no launch authority.
|
|
@@ -98,6 +102,11 @@ function verify(configuration, provider, model, options, fleet) {
|
|
|
98
102
|
if (fleet) {
|
|
99
103
|
for (const [field, expected] of Object.entries(fleet.destination)) compare(field, expected, configuration?.[field]);
|
|
100
104
|
for (const [field, expected] of Object.entries(fleet.fleetBinding)) compare(field, expected, configuration?.fleetBinding?.[field]);
|
|
105
|
+
if (fleet.artifactScope) {
|
|
106
|
+
compare('artifactScope', fleet.artifactScope, configuration?.artifactScope,
|
|
107
|
+
canonical(fleet.artifactScope) === canonical(configuration?.artifactScope));
|
|
108
|
+
compare('isolation', 'shared', configuration?.isolation);
|
|
109
|
+
}
|
|
101
110
|
}
|
|
102
111
|
let receiptTerms;
|
|
103
112
|
if (fleet && configuration?.fleetReceipt !== undefined) {
|
|
@@ -175,7 +184,7 @@ function plan(argv, io) {
|
|
|
175
184
|
const recommendation = JSON.parse(recommendationJSON);
|
|
176
185
|
fleet = bindFleet(readJSON(args['--fleet-input']), recommendation,
|
|
177
186
|
args['--config-digest'], Number(args['--now']), readFileSync(args['--capabilities'], 'utf8'),
|
|
178
|
-
resolution, role.id === 'axstack-author');
|
|
187
|
+
resolution, role.id === 'axstack-author', role.id, args['--kind']);
|
|
179
188
|
fleet.recommendation = recommendation;
|
|
180
189
|
fleet.recommendationJSON = recommendationJSON;
|
|
181
190
|
fleet.now = Number(args['--now']);
|
|
@@ -189,6 +198,7 @@ function plan(argv, io) {
|
|
|
189
198
|
const selection = { [args['--kind'] === 'launch' ? 'instanceId' : 'providerInstanceId']: providerInstanceId,
|
|
190
199
|
model, options, ...(args['--kind'] === 'task' ? fleet?.destination : {}) };
|
|
191
200
|
emit({ [args['--kind'] === 'launch' ? 'modelSelection' : 'target']: selection, runtimeMode: 'full-access',
|
|
201
|
+
...(fleet?.artifactScope ? { artifactScope: fleet.artifactScope, isolation: fleet.isolation } : {}),
|
|
192
202
|
...(fleet ? { fleetBinding: fleet.fleetBinding, ...(args['--kind'] === 'launch' ? fleet.destination : {}) } : {}),
|
|
193
203
|
...(args['--kind'] === 'task' && role.id === 'axstack-author' ? { isolation: 'worktree' } : {}) });
|
|
194
204
|
return 0;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { validScope } from './fleet-scope.js';
|
|
2
|
+
|
|
1
3
|
// Configuration is reviewed data. This module does not read account-local files.
|
|
2
4
|
export function fail(code) { throw new Error(code); }
|
|
3
5
|
export const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -58,7 +60,7 @@ export function validateInput(input, now) {
|
|
|
58
60
|
const r = input.request;
|
|
59
61
|
if (!record(r) || !text(r.task) || !['reader', 'author'].includes(r.role)
|
|
60
62
|
|| !Array.isArray(r.requiredCapabilities) || !r.requiredCapabilities.every(text)
|
|
61
|
-
|| !
|
|
63
|
+
|| !validScope(r) || !text(r.providerInstanceId) || !text(r.model)
|
|
62
64
|
|| !record(r.options) || !text(r.runtimeMode)) fail('request-schema');
|
|
63
65
|
for (const key of ['destinations', 'routeFacts', 'telemetry', 'assignments', 'pendingAssignments']) {
|
|
64
66
|
if (!Array.isArray(input[key])) fail(`${key}-schema`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Validate explicit saved native reads. These bindings do not authenticate input.
|
|
2
|
+
import { isArtifact } from './fleet-scope.js';
|
|
2
3
|
import { canonical, text, time } from './fleet-config.js';
|
|
3
4
|
|
|
4
5
|
export function inspectDestination(observation, recommendation, capabilitiesText, now) {
|
|
@@ -7,6 +8,9 @@ export function inspectDestination(observation, recommendation, capabilitiesText
|
|
|
7
8
|
state: pass ? 'pass' : missing ? 'unknown' : 'fail', evidence: evidence ?? null });
|
|
8
9
|
const s = recommendation.selected;
|
|
9
10
|
const o = observation ?? {};
|
|
11
|
+
const observed = (read, tool, environmentId, args) => read?.tool === tool
|
|
12
|
+
&& read?.environmentId === environmentId && canonical(read?.args) === canonical(args)
|
|
13
|
+
&& time(read?.observedAt) && read.observedAt <= now && text(read?.evidence);
|
|
10
14
|
for (const [name, tool, environmentId, args] of [
|
|
11
15
|
['environment', 't3_environment_read', s.environmentId, {}],
|
|
12
16
|
['project', 't3_project_read', s.environmentId, { projectId: s.projectId }],
|
|
@@ -14,22 +18,66 @@ export function inspectDestination(observation, recommendation, capabilitiesText
|
|
|
14
18
|
['links', 't3_environment_links', recommendation.originEnvironmentId, {}],
|
|
15
19
|
]) {
|
|
16
20
|
const read = o[name];
|
|
17
|
-
term('destination-observation', read
|
|
18
|
-
&& canonical(read?.args) === canonical(args) && time(read?.observedAt)
|
|
19
|
-
&& read.observedAt <= now && text(read?.evidence), read);
|
|
21
|
+
term('destination-observation', observed(read, tool, environmentId, args), read);
|
|
20
22
|
}
|
|
21
23
|
term('destination-identity', o.environment?.value?.environmentId === s.environmentId,
|
|
22
24
|
o.environment?.value?.environmentId);
|
|
23
25
|
term('destination-project', o.project?.value?.id === s.projectId && text(o.project?.value?.workspaceRoot),
|
|
24
26
|
o.project?.value);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
const project = o.project?.value;
|
|
28
|
+
let listed;
|
|
29
|
+
if (o.projectList !== undefined) {
|
|
30
|
+
const pages = o.projectList;
|
|
31
|
+
const projects = [];
|
|
32
|
+
let cursor = 0;
|
|
33
|
+
let complete = Array.isArray(pages) && pages.length > 0;
|
|
34
|
+
if (complete) for (const page of pages) {
|
|
35
|
+
const args = page?.args;
|
|
36
|
+
const limit = args?.limit ?? 20;
|
|
37
|
+
const rows = page?.value?.projects;
|
|
38
|
+
const next = page?.value?.nextCursor;
|
|
39
|
+
complete &&= cursor !== null && observed(page, 't3_project_list', s.environmentId, {
|
|
40
|
+
...(args?.cursor === undefined ? {} : { cursor }),
|
|
41
|
+
...(args?.limit === undefined ? {} : { limit }),
|
|
42
|
+
}) && (args?.cursor ?? 0) === cursor && Number.isInteger(limit) && limit >= 1 && limit <= 100
|
|
43
|
+
&& Array.isArray(rows) && rows.length <= limit
|
|
44
|
+
&& (next === null || (next === cursor + limit && rows.length === limit));
|
|
45
|
+
if (Array.isArray(rows)) projects.push(...rows);
|
|
46
|
+
cursor = next;
|
|
47
|
+
}
|
|
48
|
+
listed = projects.find((p) => p?.id === s.projectId);
|
|
49
|
+
term('destination-project-list', complete && cursor === null
|
|
50
|
+
&& projects.every((p) => text(p?.id)) && new Set(projects.map((p) => p?.id)).size === projects.length
|
|
51
|
+
&& listed?.id === s.projectId && listed.id === project?.id && listed?.workspaceRoot === project?.workspaceRoot, pages);
|
|
52
|
+
}
|
|
53
|
+
// ProjectService.listPage resolves the cold identity cache; getById need not.
|
|
54
|
+
const identities = [project?.repositoryIdentity, listed?.repositoryIdentity].filter((v) => v != null);
|
|
55
|
+
if (isArtifact(recommendation.request)) {
|
|
56
|
+
const scope = recommendation.request.scope;
|
|
57
|
+
term('destination-artifact-project', project?.repositoryIdentity === null
|
|
58
|
+
&& listed?.repositoryIdentity === null && project?.workspaceRoot === scope.workspaceRoot
|
|
59
|
+
&& s.environmentId === scope.environmentId && s.projectId === scope.projectId,
|
|
60
|
+
{ project, listed });
|
|
61
|
+
} else {
|
|
62
|
+
term('destination-repository', identities.length > 0
|
|
63
|
+
&& identities.every((v) => v.canonicalKey === s.repositoryId), identities,
|
|
64
|
+
identities.length === 0);
|
|
65
|
+
}
|
|
27
66
|
term('destination-catalog', o.capabilities?.valueText === capabilitiesText, o.capabilities?.valueText);
|
|
28
67
|
const catalog = JSON.parse(capabilitiesText);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
const providers = catalog.providers?.filter((v) => v.providerInstanceId === recommendation.request.providerInstanceId);
|
|
69
|
+
const provider = providers?.length === 1 ? providers[0] : undefined;
|
|
70
|
+
const mode = recommendation.request.runtimeMode;
|
|
71
|
+
// T3 0.1.5 resolveRuntimeMode/RuntimePolicy preserve modes without restrictions.
|
|
72
|
+
// Limit omission semantics to the observed version and native drivers we checked.
|
|
73
|
+
const unrestricted = provider?.runtimeModes === undefined && o.environment?.value?.serverVersion === '0.1.5'
|
|
74
|
+
&& ['codex', 'claudeAgent'].includes(provider?.driverKind);
|
|
75
|
+
const supported = unrestricted || (Array.isArray(provider?.runtimeModes) && provider.runtimeModes.includes(mode));
|
|
76
|
+
term('destination-execution', provider?.canRunChildTask === true && supported
|
|
77
|
+
&& catalog.runtimeMode === mode && Array.isArray(provider?.constraints) && provider.constraints.length === 0,
|
|
78
|
+
{ provider, runtimeMode: catalog.runtimeMode, serverVersion: o.environment?.value?.serverVersion },
|
|
79
|
+
providers?.length === 0 || provider?.canRunChildTask == null || catalog.runtimeMode == null
|
|
80
|
+
|| provider?.constraints == null || (provider?.runtimeModes === undefined && !unrestricted));
|
|
33
81
|
const local = s.environmentId === recommendation.originEnvironmentId;
|
|
34
82
|
const link = o.links?.value?.links?.find((v) => v.environmentId === s.environmentId);
|
|
35
83
|
term('destination-access', o.links?.value?.environmentId === recommendation.originEnvironmentId
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
// Bind supplied placement facts to a plan. No reads, dispatch or authority grant.
|
|
2
|
+
import { isArtifact } from './fleet-scope.js';
|
|
2
3
|
import { inspectDestination } from './fleet-destination.js';
|
|
3
4
|
import { canonical, record, text } from './fleet-config.js';
|
|
4
5
|
import { recommend } from './fleet-route.js';
|
|
5
6
|
|
|
6
|
-
export function bindFleet(input, saved, configDigest, now, capabilitiesText, resolved, author) {
|
|
7
|
+
export function bindFleet(input, saved, configDigest, now, capabilitiesText, resolved, author, roleId, kind) {
|
|
7
8
|
const hold = (code) => { throw Object.assign(new Error(code), { exitCode: 2 }); };
|
|
8
9
|
if (saved?.schemaVersion !== 1) hold('schema-version: recommendation');
|
|
9
10
|
if (configDigest !== input?.configDigest) hold('config-digest-mismatch');
|
|
10
11
|
let current;
|
|
11
12
|
try { current = recommend(input, now); } catch (error) { hold(error.message); }
|
|
12
13
|
if (canonical(saved) !== canonical(current)) hold('recommendation-mismatch');
|
|
14
|
+
const artifact = isArtifact(current.request);
|
|
15
|
+
if (artifact) {
|
|
16
|
+
if (roleId !== current.request.scope.roleId) hold('artifact-role-mismatch');
|
|
17
|
+
if (kind !== 'task') hold('artifact-task-only');
|
|
18
|
+
if (input.nativeDestination === undefined) hold('artifact-native-destination-required');
|
|
19
|
+
}
|
|
13
20
|
const selected = current.selected;
|
|
14
21
|
if (!selected) hold('no-executable-route');
|
|
15
22
|
// Coordinator enablement/lifecycle is a later capability, never inferred here.
|
|
@@ -40,7 +47,8 @@ export function bindFleet(input, saved, configDigest, now, capabilitiesText, res
|
|
|
40
47
|
return { id, value };
|
|
41
48
|
});
|
|
42
49
|
return {
|
|
43
|
-
options,
|
|
50
|
+
options, ...(artifact ? { artifactScope: current.request.scope, isolation: 'shared' } : {}),
|
|
51
|
+
destination: { environmentId: selected.environmentId, projectId: selected.projectId },
|
|
44
52
|
fleetBinding: { configDigest, destinationSnapshotId: selected.destinationSnapshotId,
|
|
45
53
|
destinationCapabilitiesDigest: digest },
|
|
46
54
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isArtifact } from './fleet-scope.js';
|
|
1
2
|
import { nativeAssignments } from './fleet-inventory.js';
|
|
2
3
|
import { canonical, evidence, fail, record, text, time } from './fleet-config.js';
|
|
3
4
|
|
|
@@ -116,6 +117,8 @@ export function routeReasons(route, host, destination, facts, input, now) {
|
|
|
116
117
|
const add = (code) => { if (!reasons.includes(code)) reasons.push(code); };
|
|
117
118
|
const remote = route.environmentId !== input.origin.environmentId;
|
|
118
119
|
const r = input.request;
|
|
120
|
+
const artifact = isArtifact(r);
|
|
121
|
+
if (artifact && (remote || route.kind !== 'local' || r.scope.environmentId !== input.origin.environmentId)) add('artifact-local-only');
|
|
119
122
|
if (Object.hasOwn(input.exclusions.hosts, host.id)) add('excluded-host');
|
|
120
123
|
if (Object.hasOwn(input.exclusions.routes, route.id)) add('excluded-route');
|
|
121
124
|
if (!route.enabled) add('route-disabled');
|
|
@@ -134,7 +137,9 @@ export function routeReasons(route, host, destination, facts, input, now) {
|
|
|
134
137
|
|| !evidence(destination.evidence, host.environmentId, now)) add('evidence-unproven');
|
|
135
138
|
if (!text(destination.snapshotId) || facts?.destinationSnapshotId !== destination.snapshotId) add('target-snapshot-mismatch');
|
|
136
139
|
if (!record(destination.project) || !text(destination.project.id)) add('project-missing');
|
|
137
|
-
else if (destination.project.repositoryId !== r.repositoryId
|
|
140
|
+
else if (destination.project.repositoryId !== r.repositoryId
|
|
141
|
+
|| (artifact && (destination.project.id !== r.scope.projectId
|
|
142
|
+
|| destination.project.workspaceRoot !== r.scope.workspaceRoot))) add('project-mismatch');
|
|
138
143
|
const e = destination.execution;
|
|
139
144
|
if (!record(e) || e.available !== true) add('execution-unproven');
|
|
140
145
|
else if (e.providerInstanceId !== r.providerInstanceId || e.model !== r.model
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// T3 0.1.5 remote status omits options and a result-run ID. Never synthesize them.
|
|
2
|
+
import { canonical, text, time } from './fleet-config.js';
|
|
3
|
+
|
|
4
|
+
export function inspectNativeFamily(receipt, expected) {
|
|
5
|
+
const r = receipt;
|
|
6
|
+
const n = r.nativeFamily;
|
|
7
|
+
const s = expected.recommendation.selected;
|
|
8
|
+
const a = expected.attempt;
|
|
9
|
+
const child = r.status?.value?.remoteChild?.threadId;
|
|
10
|
+
const current = r.thread?.value?.thread?.latestRunId;
|
|
11
|
+
const terms = [];
|
|
12
|
+
const term = (pass, evidence) => terms.push({ code: 'receipt-native-family',
|
|
13
|
+
state: pass ? 'pass' : evidence == null ? 'unknown' : 'fail', evidence: evidence ?? null });
|
|
14
|
+
term(n?.tool === 'orchestration.getThreadFamily' && n?.transport === 't3-native-rpc'
|
|
15
|
+
&& n?.environmentId === s.environmentId && n?.serverVersion === '0.1.5'
|
|
16
|
+
&& s.versions?.t3 === n.serverVersion && canonical(n?.args) === canonical({ threadId: child })
|
|
17
|
+
&& time(n?.observedAt) && n.observedAt <= expected.now
|
|
18
|
+
&& n.observedAt >= r.thread?.observedAt && n.observedAt >= r.terminal?.observedAt
|
|
19
|
+
&& text(n?.evidence), n);
|
|
20
|
+
const rows = n?.value?.threads;
|
|
21
|
+
const matches = Array.isArray(rows) ? rows.filter((v) => v?.id === child) : [];
|
|
22
|
+
term(time(n?.value?.snapshotSequence) && Array.isArray(rows)
|
|
23
|
+
&& rows.every((v) => text(v?.id)) && new Set(rows.map((v) => v.id)).size === rows.length
|
|
24
|
+
&& matches.length === 1, n?.value);
|
|
25
|
+
const shell = matches[0];
|
|
26
|
+
term(shell?.id === child && shell?.projectId === s.projectId && shell?.title === a?.title
|
|
27
|
+
&& shell?.latestRunId === current && text(current)
|
|
28
|
+
&& shell?.delegatedFrom?.environmentId === a?.origin?.environmentId
|
|
29
|
+
&& shell?.delegatedFrom?.threadId === a?.origin?.threadId, shell);
|
|
30
|
+
term(shell?.status === 'completed' && Array.isArray(rows)
|
|
31
|
+
&& rows.every((v) => ['completed', 'idle'].includes(v?.status) && v?.activeRunId === null
|
|
32
|
+
&& Array.isArray(v?.pendingBackgroundTasks) && v.pendingBackgroundTasks.length === 0
|
|
33
|
+
&& v?.pendingRuntimeRequest === null && v?.pendingGuestMessageCount === 0), rows);
|
|
34
|
+
return { terms, shell };
|
|
35
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Saved evidence validator. A pass binds supplied facts; it cannot authenticate them.
|
|
2
2
|
import { canonical, record, text, time } from './fleet-config.js';
|
|
3
|
+
import { inside, isArtifact } from './fleet-scope.js';
|
|
4
|
+
import { inspectNativeFamily } from './fleet-native-receipt.js';
|
|
3
5
|
|
|
4
6
|
function options(value) {
|
|
5
7
|
if (!Array.isArray(value)) return record(value) ? value : null;
|
|
@@ -20,6 +22,7 @@ export function inspectFleetReceipt(receipt, expected) {
|
|
|
20
22
|
const a = expected.attempt;
|
|
21
23
|
const s = expected.recommendation.selected;
|
|
22
24
|
const request = expected.recommendation.request;
|
|
25
|
+
const artifact = isArtifact(request);
|
|
23
26
|
term('receipt-origin', text(a?.origin?.threadId) && a?.origin?.environmentId === expected.recommendation.originEnvironmentId
|
|
24
27
|
&& same(r.attempt?.origin, a?.origin), r.attempt?.origin,
|
|
25
28
|
!text(a?.origin?.threadId) || r.attempt?.origin == null);
|
|
@@ -57,12 +60,30 @@ export function inspectFleetReceipt(receipt, expected) {
|
|
|
57
60
|
&& target?.providerInstanceId === request.providerInstanceId && target?.model === request.model
|
|
58
61
|
&& same(options(target?.options), request.options) && r.requested?.runtimeMode === request.runtimeMode,
|
|
59
62
|
r.requested);
|
|
63
|
+
const native = r.nativeFamily === undefined ? null : inspectNativeFamily(r, expected);
|
|
64
|
+
if (native) terms.push(...native.terms);
|
|
65
|
+
const shell = native?.shell;
|
|
66
|
+
const nativeValid = remote && native?.terms.every((t) => t.state === 'pass');
|
|
67
|
+
if (native) term('receipt-effective', shell?.providerInstanceId === request.providerInstanceId
|
|
68
|
+
&& shell?.modelSelection?.instanceId === request.providerInstanceId
|
|
69
|
+
&& shell?.modelSelection?.model === request.model
|
|
70
|
+
&& Array.isArray(shell?.modelSelection?.options)
|
|
71
|
+
&& same(options(shell?.modelSelection?.options), request.options)
|
|
72
|
+
&& shell?.runtimeMode === request.runtimeMode, shell?.modelSelection);
|
|
73
|
+
const runs = r.thread?.value?.recentRuns;
|
|
74
|
+
// Without the origin result-run ID, only the sole initial run can own its summary.
|
|
75
|
+
const initial = nativeValid && status?.childThreadId === null && submitted?.childThreadId === null
|
|
76
|
+
&& status?.childRunId === null && submitted?.childRunId === null
|
|
77
|
+
&& status?.latestTerminalRunId === null && thread?.runCount === 1
|
|
78
|
+
&& Array.isArray(runs) && runs.length === 1 && runs[0]?.ordinal === 1;
|
|
79
|
+
const effectiveOptions = status?.options === undefined && initial
|
|
80
|
+
? shell?.modelSelection?.options : status?.options;
|
|
60
81
|
term('receipt-effective', status?.providerInstanceId === request.providerInstanceId
|
|
61
82
|
&& status?.model === request.model && thread?.providerInstanceId === request.providerInstanceId
|
|
62
|
-
&& thread?.model === request.model && same(options(
|
|
83
|
+
&& thread?.model === request.model && same(options(effectiveOptions), request.options)
|
|
63
84
|
&& thread?.runtimeMode === request.runtimeMode, { provider: status?.providerInstanceId,
|
|
64
|
-
model: status?.model, options:
|
|
65
|
-
|
|
85
|
+
model: status?.model, options: effectiveOptions ?? null, runtimeMode: thread?.runtimeMode },
|
|
86
|
+
effectiveOptions == null || thread?.runtimeMode == null);
|
|
66
87
|
const terminal = r.terminal?.value;
|
|
67
88
|
term('receipt-terminal', status?.status === 'completed' && status?.latestTerminalStatus === 'completed'
|
|
68
89
|
&& status?.workState === 'result_available' && status?.hasPendingChildRuns === false
|
|
@@ -84,9 +105,8 @@ export function inspectFleetReceipt(receipt, expected) {
|
|
|
84
105
|
&& questions.value.requests.length === 0, questions,
|
|
85
106
|
questions == null || questions.value?.requests === undefined);
|
|
86
107
|
const current = thread?.latestRunId;
|
|
87
|
-
const runs = r.thread?.value?.recentRuns;
|
|
88
108
|
const latest = Array.isArray(runs) ? runs.find((v) => v.runId === current) : null;
|
|
89
|
-
term('receipt-run', text(current) && status?.latestTerminalRunId === current
|
|
109
|
+
term('receipt-run', text(current) && (status?.latestTerminalRunId === current || initial)
|
|
90
110
|
&& terminal?.threadId === child(status) && terminal?.runId === current
|
|
91
111
|
&& (!terminal?.resultRun || (terminal.resultRun.runId === current && terminal.resultRun.status === 'completed'))
|
|
92
112
|
&& (!remote || (r.remoteChild?.environmentId === s.environmentId
|
|
@@ -94,7 +114,7 @@ export function inspectFleetReceipt(receipt, expected) {
|
|
|
94
114
|
&& latest?.status === 'completed' && Number.isSafeInteger(latest?.ordinal)
|
|
95
115
|
&& runs.every((v) => Number.isSafeInteger(v.ordinal) && v.ordinal <= latest.ordinal),
|
|
96
116
|
{ current, originTerminal: status?.latestTerminalRunId, terminal, runs },
|
|
97
|
-
!current || !status?.latestTerminalRunId || !terminal?.runId || !Array.isArray(runs)
|
|
117
|
+
!current || (!status?.latestTerminalRunId && !initial) || !terminal?.runId || !Array.isArray(runs)
|
|
98
118
|
|| (remote && !r.remoteChild?.runId));
|
|
99
119
|
const marker = `AXSTACK-DONE key=${a?.key} head=${a?.candidate} report=${r.evidence?.path}`;
|
|
100
120
|
const finalMarker = (value) => {
|
|
@@ -109,15 +129,46 @@ export function inspectFleetReceipt(receipt, expected) {
|
|
|
109
129
|
};
|
|
110
130
|
const items = r.thread?.value?.items;
|
|
111
131
|
const final = Array.isArray(items) ? items.filter((v) => v.runId === current && v.createdBy === 'agent').at(-1) : null;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
// T3 0.1.5 suppresses latestTerminalSummary when it equals summary.
|
|
133
|
+
// Only the same local result run may use that deduplicated value.
|
|
134
|
+
const localSummary = artifact && !remote && s.versions?.t3 === '0.1.5'
|
|
135
|
+
&& status?.latestTerminalSummary === null && text(current)
|
|
136
|
+
&& status?.childRunId === current && status?.latestTerminalRunId === current
|
|
137
|
+
&& status?.status === 'completed' && status?.summary === final?.text;
|
|
138
|
+
const resultSummary = localSummary ? status.summary : status?.latestTerminalSummary;
|
|
139
|
+
term('receipt-marker', (artifact ? /^[a-f0-9]{64}$/ : /^[a-f0-9]{40}$/).test(a?.candidate ?? '')
|
|
140
|
+
&& finalMarker(resultSummary) === marker && finalMarker(final?.text) === marker
|
|
141
|
+
&& (!initial || resultSummary === final?.text),
|
|
142
|
+
{ origin: resultSummary, destination: final?.text });
|
|
115
143
|
term('receipt-host-path', r.evidence?.hostId === s.hostId && r.evidence?.environmentId === s.environmentId
|
|
116
|
-
&& absolute(r.evidence?.path) && finalMarker(
|
|
117
|
-
if (
|
|
144
|
+
&& absolute(r.evidence?.path) && finalMarker(resultSummary)?.endsWith(` report=${r.evidence.path}`), r.evidence);
|
|
145
|
+
if (artifact) {
|
|
146
|
+
const scope = request.scope;
|
|
147
|
+
const bytes = r.artifact;
|
|
148
|
+
term('receipt-artifact-scope', a?.base === null && same(r.artifactScope, scope)
|
|
149
|
+
&& inside(r.evidence?.path, scope.evidenceRoot), r.artifactScope);
|
|
150
|
+
term('receipt-artifact-bytes', bytes?.sha256 === a?.candidate && inside(bytes?.path, scope.artifactRoot)
|
|
151
|
+
&& bytes?.readback?.path === bytes.path && bytes?.readback?.sha256 === bytes.sha256
|
|
152
|
+
&& text(bytes?.readback?.evidence), bytes);
|
|
153
|
+
// Shared native children can inherit a parent's non-Git scratch directory.
|
|
154
|
+
// Bind that value to an actual parent read; a path alone proves nothing.
|
|
155
|
+
const parent = r.parent;
|
|
156
|
+
const parentThread = parent?.value?.thread;
|
|
157
|
+
const inherited = !remote && s.versions?.t3 === '0.1.5'
|
|
158
|
+
&& parent?.tool === 't3_thread_read' && parent.environmentId === a?.origin?.environmentId
|
|
159
|
+
&& same(parent.args, { threadId: a?.origin?.threadId })
|
|
160
|
+
&& time(parent.observedAt) && parent.observedAt <= expected.now && text(parent.evidence)
|
|
161
|
+
&& parentThread?.threadId === a?.origin?.threadId && parentThread?.projectId === s.projectId
|
|
162
|
+
&& thread?.parentThreadId === parentThread?.threadId && thread?.relationshipToParent === 'subagent'
|
|
163
|
+
&& absolute(parentThread?.worktreePath) && inside(parentThread.worktreePath, scope.workspaceRoot)
|
|
164
|
+
&& thread?.worktreePath === parentThread.worktreePath;
|
|
165
|
+
term('receipt-isolation', r.requested?.isolation === 'shared'
|
|
166
|
+
&& (thread?.worktreePath === null || inherited) && r.checkout == null,
|
|
167
|
+
{ isolation: r.requested?.isolation, worktreePath: thread?.worktreePath, parent });
|
|
168
|
+
} else if (a?.title?.startsWith('Reviewer: ')) {
|
|
118
169
|
term('receipt-isolation', r.requested?.isolation === 'worktree', r.requested?.isolation);
|
|
119
170
|
}
|
|
120
|
-
if (a?.title?.startsWith('Reviewer: ') || r.checkout != null || thread?.worktreePath != null || r.requested?.isolation === 'worktree') {
|
|
171
|
+
if (!artifact && (a?.title?.startsWith('Reviewer: ') || r.checkout != null || thread?.worktreePath != null || r.requested?.isolation === 'worktree')) {
|
|
121
172
|
const c = r.checkout;
|
|
122
173
|
term('receipt-checkout', c?.hostId === s.hostId && c?.environmentId === s.environmentId
|
|
123
174
|
&& absolute(c?.path) && c.path === thread?.worktreePath
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Explicit artifact authority, separate from repository engineering authority.
|
|
2
|
+
const sourceRoles = ['axstack-research-requirements', 'axstack-research-code', 'axstack-research-code-sol',
|
|
3
|
+
'axstack-explore-execution', 'axstack-explore-execution-sol',
|
|
4
|
+
'axstack-research-web', 'axstack-research-web-sol', 'axstack-research-x', 'axstack-diligence'];
|
|
5
|
+
const articleRoles = ['axstack-explainer', 'axstack-explainer-review', 'axstack-ui-verifier', 'axstack-ui-verifier-sonnet'];
|
|
6
|
+
export const isArtifact = (request) => request?.scope?.kind === 'local-artifact';
|
|
7
|
+
const path = (value) => typeof value === 'string' && value.startsWith('/') && value !== '/'
|
|
8
|
+
&& !/[\s\x00-\x1f\x7f]/.test(value) && !value.slice(1).split('/').some((v) => ['', '.', '..'].includes(v));
|
|
9
|
+
export const inside = (value, root) => path(value) && path(root) && value.startsWith(`${root}/`);
|
|
10
|
+
const overlaps = (a, b) => a === b || inside(a, b) || inside(b, a);
|
|
11
|
+
|
|
12
|
+
export function validScope(request) {
|
|
13
|
+
const s = request.scope;
|
|
14
|
+
if (s === undefined) return typeof request.repositoryId === 'string' && request.repositoryId.trim().length > 0;
|
|
15
|
+
if (s?.kind === 'repository') return typeof request.repositoryId === 'string' && request.repositoryId.trim().length > 0;
|
|
16
|
+
if (!isArtifact(request) || request.repositoryId !== null || request.role !== 'reader'
|
|
17
|
+
|| !['research', 'explain', 'publish'].includes(s.workflow)
|
|
18
|
+
|| ![...sourceRoles, ...(s.workflow === 'research' ? [] : articleRoles)].includes(s.roleId)
|
|
19
|
+
|| ![s.environmentId, s.projectId].every((v) => typeof v === 'string' && v.trim().length > 0)
|
|
20
|
+
|| !inside(s.artifactRoot, s.workspaceRoot) || !inside(s.evidenceRoot, s.workspaceRoot)
|
|
21
|
+
|| overlaps(s.artifactRoot, s.evidenceRoot) || !Array.isArray(s.reservedRoots)
|
|
22
|
+
|| s.reservedRoots.some((root) => !path(root) || overlaps(root, s.artifactRoot) || overlaps(root, s.evidenceRoot))) return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
@@ -20,6 +20,10 @@ scope baseline. Ordinary work in the current chat needs no launch preflight;
|
|
|
20
20
|
load the [T3 runtime boundary](../axstack/references/t3-runtime.md) only
|
|
21
21
|
immediately before an actual profile dispatch.
|
|
22
22
|
|
|
23
|
+
For explicit local non-repository work, apply
|
|
24
|
+
[Local artifacts](../axstack/references/local-artifacts.md) before native dispatch.
|
|
25
|
+
Preserve this phase's independent source/fact and exact-byte review requirements.
|
|
26
|
+
|
|
23
27
|
## 1. Bound the question and evidence
|
|
24
28
|
|
|
25
29
|
1. Identify the audience, question, requested format, theme, and publication
|
|
@@ -24,7 +24,12 @@ Interaction checks remain with `axstack-ui-verifier`; source-fidelity review is
|
|
|
24
24
|
Use this checklist for every HTML explanation and other visual artifacts where
|
|
25
25
|
rendering matters.
|
|
26
26
|
|
|
27
|
-
Browser and visual checks
|
|
27
|
+
Browser and visual checks use delegated `axstack-ui-verifier`: engineering
|
|
28
|
+
candidates require its own detached checkout; explicit non-repository articles
|
|
29
|
+
use the local shared delegate with an owned exact-byte snapshot under
|
|
30
|
+
[Local artifacts](../../axstack/references/local-artifacts.md). Outputs stay in
|
|
31
|
+
its private evidence folder. Playwright checks and source/design review remain
|
|
32
|
+
bound to the exact final SHA-256 before public publication.
|
|
28
33
|
|
|
29
34
|
1. Identify the final artifact bytes and theme. The explicit user theme wins;
|
|
30
35
|
otherwise use `prefers-color-scheme`.
|
|
@@ -10,6 +10,10 @@ The current T3 driver owns publication; workers return checked artifacts.
|
|
|
10
10
|
Load [Standing contracts](../axstack/references/contracts.md) and follow its
|
|
11
11
|
lifecycle pointers. Keep page bytes, checks and receipts in private run evidence.
|
|
12
12
|
|
|
13
|
+
For explicit local non-repository work, apply
|
|
14
|
+
[Local artifacts](../axstack/references/local-artifacts.md) before native dispatch.
|
|
15
|
+
Preserve this phase's independent source/fact and exact-byte review requirements.
|
|
16
|
+
|
|
13
17
|
## 1. Bind the request
|
|
14
18
|
|
|
15
19
|
Read host-managed `~/.config/axstack/publish.json` containing string fields
|
|
@@ -20,6 +20,10 @@ baseline, ticket map, or author pipeline. Keep it read-only unless requested
|
|
|
20
20
|
and separately authorized to change product code or publish; otherwise, neither
|
|
21
21
|
is part of research.
|
|
22
22
|
|
|
23
|
+
For explicit local non-repository work, apply
|
|
24
|
+
[Local artifacts](../axstack/references/local-artifacts.md) before native dispatch.
|
|
25
|
+
Preserve this phase's independent source/fact and exact-byte review requirements.
|
|
26
|
+
|
|
23
27
|
## Procedure
|
|
24
28
|
|
|
25
29
|
1. **Frame the question.** Restate the question, what is in scope, what is
|
|
@@ -11,6 +11,11 @@ Include [Safe deletion](../axstack/references/workspace-hygiene.md#safe-deletion
|
|
|
11
11
|
|
|
12
12
|
Manual review keeps the user’s chat and workspace open.
|
|
13
13
|
|
|
14
|
+
For non-repository article/source review, follow
|
|
15
|
+
[Local artifacts](../axstack/references/local-artifacts.md); Explain/Publish own
|
|
16
|
+
the independent artifact review. PR/code-review modes below retain repository
|
|
17
|
+
and native worktree requirements; null identity never selects those modes.
|
|
18
|
+
|
|
14
19
|
Produce evidence-bound findings for an exact revision using the review count
|
|
15
20
|
and model routing required by its mode. Report within the requested authority;
|
|
16
21
|
for own PRs, automatic merge is the default under the
|