@raishin/vanguard-frontier-agentic 3.1.0 → 3.2.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanguard-frontier-agentic",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Curated marketplace for cloud and zero-trust AI workflows. 331 agents, 286 skills, and rules across AWS, Azure, OCI, GCP, Alibaba Cloud, Huawei Cloud, Kubernetes, and Terraform.",
5
5
  "author": {
6
6
  "name": "Raishin",
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/Raishin/vanguard-frontier-agentic/schemas/model-policy.schema.json",
4
+ "title": "VFA Model Policy",
5
+ "description": "Per-harness model and reasoning-effort policy for agent harness variants. Canonical intent consumed by scripts/model-policy.mjs, which projects it into executable harness files and generates catalog/model-assignments.json. Precedence: agent > role > provider > all; 'auto' omits the managed field so the harness runtime default applies.",
6
+ "type": "object",
7
+ "required": ["manifest_version", "rules"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "manifest_version": {
11
+ "const": 1
12
+ },
13
+ "description": {
14
+ "type": "string"
15
+ },
16
+ "defaults": {
17
+ "type": "object",
18
+ "description": "Informational: the implicit baseline when no rule matches. Both fields are constitutionally 'auto'.",
19
+ "required": ["model", "reasoning_effort"],
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "model": { "const": "auto" },
23
+ "reasoning_effort": { "const": "auto" }
24
+ }
25
+ },
26
+ "rules": {
27
+ "type": "array",
28
+ "items": {
29
+ "type": "object",
30
+ "required": ["scope", "harness"],
31
+ "additionalProperties": false,
32
+ "anyOf": [
33
+ { "required": ["model"] },
34
+ { "required": ["reasoning_effort"] }
35
+ ],
36
+ "properties": {
37
+ "scope": {
38
+ "type": "string",
39
+ "pattern": "^(all|(provider|role|agent):[a-z0-9][a-z0-9-]*)$",
40
+ "description": "Targeting scope: 'all', 'provider:<id>', 'role:<id>' (install-roles.json), or 'agent:<id>'."
41
+ },
42
+ "harness": {
43
+ "type": "string",
44
+ "enum": ["codex", "copilot", "claude-code", "cursor", "gemini", "kiro"]
45
+ },
46
+ "model": {
47
+ "type": "string",
48
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]*$",
49
+ "description": "Model name for this harness, or 'auto' to clear the field. This pattern is shape only (union of every harness's charset, including codex's ':' for Ollama name:tag and '/' for OpenRouter author/model); registry membership per harness is enforced by scripts/model-policy.mjs check against catalog/model-registry.json."
50
+ },
51
+ "reasoning_effort": {
52
+ "type": "string",
53
+ "enum": ["auto", "none", "minimal", "low", "medium", "high", "xhigh", "max"],
54
+ "description": "Reasoning effort for harnesses that support it (codex, claude-code today). This is the union vocabulary across harnesses; the per-harness subset (codex: none|minimal|low|medium|high|xhigh; claude-code: low|medium|high|xhigh|max; cursor: no reasoning_effort field — unsupported, not even 'none') is enforced by scripts/model-policy.mjs check against catalog/model-registry.json. 'auto' clears the field."
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,127 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/Raishin/vanguard-frontier-agentic/schemas/model-registry.schema.json",
4
+ "title": "VFA Model Registry",
5
+ "description": "Verified model-name and reasoning-effort capability matrix per harness. Consumed by scripts/model-policy.mjs to fail closed on values that would 404 (model_not_found) or be silently dropped at request time. Namespaces are evaluated in array order; the first whose 'match' pattern accepts the value classifies it. 'closed' membership requires the model to be listed in 'models'; 'open' namespaces (catalogs too large to enumerate, e.g. Ollama/OpenRouter) validate shape only. Refresh via .claude/skills/model-registry-refresh/SKILL.md; human-readable companion: docs/model-policy-matrix.md.",
6
+ "type": "object",
7
+ "required": ["manifest_version", "last_refreshed", "harnesses"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "manifest_version": { "const": 1 },
11
+ "description": { "type": "string" },
12
+ "last_refreshed": {
13
+ "type": "string",
14
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
15
+ "description": "Date the registry as a whole was last re-verified against official documentation."
16
+ },
17
+ "harnesses": {
18
+ "type": "object",
19
+ "required": ["codex", "claude-code", "cursor"],
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "codex": { "$ref": "#/definitions/harness" },
23
+ "claude-code": { "$ref": "#/definitions/harness" },
24
+ "cursor": { "$ref": "#/definitions/harness" }
25
+ }
26
+ }
27
+ },
28
+ "definitions": {
29
+ "harness": {
30
+ "type": "object",
31
+ "required": ["reasoning_key", "reasoning_efforts", "namespaces"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "reasoning_key": {
35
+ "type": ["string", "null"],
36
+ "description": "Config key the harness uses for reasoning effort (codex: model_reasoning_effort in codex.toml; claude-code: effort in frontmatter). null = harness has no projectable reasoning field."
37
+ },
38
+ "reasoning_efforts": {
39
+ "type": "array",
40
+ "items": { "type": "string", "pattern": "^[a-z]+$" },
41
+ "description": "Complete vocabulary of effort values the harness accepts. Empty = no reasoning field."
42
+ },
43
+ "reasoning_note": { "type": "string" },
44
+ "namespaces": {
45
+ "type": "array",
46
+ "minItems": 1,
47
+ "items": { "$ref": "#/definitions/namespace" }
48
+ }
49
+ }
50
+ },
51
+ "namespace": {
52
+ "type": "object",
53
+ "required": ["id", "match", "membership"],
54
+ "additionalProperties": false,
55
+ "properties": {
56
+ "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
57
+ "description": { "type": "string" },
58
+ "match": {
59
+ "type": "string",
60
+ "description": "Anchored (^...$) regular expression classifying a model value into this namespace. Evaluated in array order; first match wins."
61
+ },
62
+ "membership": {
63
+ "enum": ["closed", "open"],
64
+ "description": "closed: value must appear in 'models' (fail closed on unregistered names). open: shape-validated only; catalog too large/volatile to enumerate."
65
+ },
66
+ "model_provider": {
67
+ "type": ["string", "null"],
68
+ "description": "codex only: value projected as model_provider in codex.toml when a model in this namespace is assigned. null = default provider (no line projected)."
69
+ },
70
+ "requires_provider_table": {
71
+ "type": "boolean",
72
+ "description": "codex only: the operator's codex config must define a matching [model_providers.<id>] table for this route to work."
73
+ },
74
+ "reasoning_efforts": {
75
+ "type": "array",
76
+ "items": { "type": "string", "pattern": "^[a-z]+$" },
77
+ "description": "Effort values verified to work for models routed through this namespace. Overrides the harness vocabulary; empty = pinning reasoning onto this namespace fails check (fail closed)."
78
+ },
79
+ "reasoning_note": { "type": "string" },
80
+ "sources": {
81
+ "type": "array",
82
+ "items": { "type": "string" },
83
+ "description": "Official documentation URLs / Context7 library IDs backing this namespace's entries."
84
+ },
85
+ "examples": {
86
+ "type": "array",
87
+ "items": { "type": "string" },
88
+ "description": "Advisory sample values for open namespaces (not an allowlist)."
89
+ },
90
+ "models": {
91
+ "type": "array",
92
+ "items": { "$ref": "#/definitions/model" },
93
+ "description": "Verified allowlist for closed namespaces."
94
+ }
95
+ }
96
+ },
97
+ "model": {
98
+ "type": "object",
99
+ "required": ["id", "last_verified"],
100
+ "additionalProperties": false,
101
+ "properties": {
102
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]*$" },
103
+ "reasoning_efforts": {
104
+ "type": "array",
105
+ "items": { "type": "string", "pattern": "^[a-z]+$" },
106
+ "description": "Effort values this model supports. Omitted = inherit the namespace (or harness) vocabulary. Empty array = non-reasoning model; pinning any effort fails check."
107
+ },
108
+ "last_verified": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
109
+ "source": { "type": "string" },
110
+ "note": { "type": "string" },
111
+ "status": {
112
+ "enum": ["available", "retiring", "retired"],
113
+ "description": "Provider lifecycle state. Omitted = available. 'retiring' = provider announced a shutdown; policies pinning it get warnings everywhere (check/apply/report, assignments index, TUI). 'retired' = shutdown effective; projection falls back to the documented successor (chain-followed) with a loud warning until the policy is migrated. Behavior is driven ONLY by this committed field, never by the wall clock — flip it via the model-registry-refresh workflow."
114
+ },
115
+ "retirement_date": {
116
+ "type": "string",
117
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
118
+ "description": "Provider-announced shutdown date (informational, surfaced in warnings; does not itself change projection)."
119
+ },
120
+ "successor": {
121
+ "type": "string",
122
+ "description": "Documented replacement model id within the same namespace. Required when status is 'retired' (it is the fallback target); strongly recommended for 'retiring'. Must reference an existing model entry; chains must terminate at a non-retired model."
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
@@ -273,16 +273,6 @@ function ensurePlatform(platform) {
273
273
  }
274
274
 
275
275
  function assertWithin(parent, child, label) {
276
- // Lexical containment check — path.resolve() is purely string-based and does
277
- // NOT follow symlinks. This guards against traversal strings (../../) and
278
- // metadata.json with absolute paths outside the repo.
279
- //
280
- // Residual TOCTOU: if an adversary races to create a symlink at `child`
281
- // AFTER this check but BEFORE the actual write, the symlink target would be
282
- // written to. copyFile()/copySkillTree() use lstatSync on source AND
283
- // destination to detect pre-existing symlinks, which closes the window for
284
- // the common case. A fully TOCTOU-proof solution requires O_NOFOLLOW at the
285
- // kernel level, which is not exposed by Node.js fs APIs.
286
276
  const resolvedParent = path.resolve(parent);
287
277
  const resolvedChild = path.resolve(child);
288
278
  const sep = path.sep;
@@ -295,6 +285,47 @@ function assertWithin(parent, child, label) {
295
285
  }
296
286
  }
297
287
 
288
+ function assertNoSymlinkAncestor(parent, child, label) {
289
+ assertWithin(parent, child, label);
290
+
291
+ const resolvedParent = path.resolve(parent);
292
+ const resolvedChild = path.resolve(child);
293
+ const relativeParent = path.dirname(path.relative(resolvedParent, resolvedChild));
294
+ if (!relativeParent || relativeParent === ".") return;
295
+
296
+ let current = resolvedParent;
297
+ for (const part of relativeParent.split(path.sep)) {
298
+ current = path.join(current, part);
299
+ let stat;
300
+ try {
301
+ stat = fs.lstatSync(current);
302
+ } catch (err) {
303
+ if (err && err.code === "ENOENT") return;
304
+ throw err;
305
+ }
306
+ if (stat.isSymbolicLink()) {
307
+ throw new Error(
308
+ `Refusing to ${label}: symbolic link ancestor '${current}' would redirect writes outside '${resolvedParent}'. ` +
309
+ `Remove the symlink and retry.`
310
+ );
311
+ }
312
+ if (!stat.isDirectory()) {
313
+ throw new Error(
314
+ `Refusing to ${label}: path ancestor '${current}' is not a directory.`
315
+ );
316
+ }
317
+ }
318
+ }
319
+
320
+ function assertSafeWriteDestination(parent, destination, label) {
321
+ // Lexical containment blocks traversal strings and absolute-path metadata.
322
+ // The ancestor walk then rejects pre-existing symlinks such as `.codex -> /x`
323
+ // before mkdir/copy can follow them. Node does not expose fully race-proof
324
+ // O_NOFOLLOW directory creation, so this intentionally closes the reachable
325
+ // planted-symlink case without pretending to solve privileged TOCTOU races.
326
+ assertNoSymlinkAncestor(parent, destination, label);
327
+ }
328
+
298
329
  function loadSkills() {
299
330
  const skillsRoot = path.join(repoRoot, "skills");
300
331
  if (!fs.existsSync(skillsRoot)) return new Map();
@@ -325,11 +356,12 @@ function loadSkills() {
325
356
  return byName;
326
357
  }
327
358
 
328
- function copySkillTree(sourceDir, destDir, force) {
359
+ function copySkillTree(sourceDir, destDir, force, targetRoot) {
329
360
  assertWithin(repoRoot, sourceDir, "read skill source");
330
361
  for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
331
362
  const src = path.join(sourceDir, entry.name);
332
363
  const dst = path.join(destDir, entry.name);
364
+ assertSafeWriteDestination(targetRoot, dst, "write skill tree destination");
333
365
  if (entry.isSymbolicLink()) {
334
366
  throw new Error(`Refusing to copy symbolic link in skill tree: ${src}`);
335
367
  }
@@ -342,7 +374,7 @@ function copySkillTree(sourceDir, destDir, force) {
342
374
  );
343
375
  }
344
376
  if (entry.isDirectory()) {
345
- copySkillTree(src, dst, force);
377
+ copySkillTree(src, dst, force, targetRoot);
346
378
  continue;
347
379
  }
348
380
  if (!entry.isFile()) continue;
@@ -404,7 +436,8 @@ function resolveCompanionSkills(selectedAgents, skillsByName, role, includeAll,
404
436
  return { skillNames: [...skillNames].sort(), orphans };
405
437
  }
406
438
 
407
- function copyFile(source, destination, force) {
439
+ function copyFile(source, destination, force, targetRoot) {
440
+ assertSafeWriteDestination(targetRoot, destination, "write file destination");
408
441
  const sourceStat = fs.lstatSync(source);
409
442
  if (sourceStat.isSymbolicLink()) {
410
443
  throw new Error(`Refusing to copy symbolic link as harness source: ${source}`);
@@ -662,7 +695,7 @@ function main() {
662
695
 
663
696
  for (const operation of operations) {
664
697
  assertWithin(args.repo, operation.dest, "write destination");
665
- copyFile(operation.source, operation.dest, args.force);
698
+ copyFile(operation.source, operation.dest, args.force, args.repo);
666
699
  if (platform === "codex") {
667
700
  rewriteCodexAgentSkillPaths(operation.dest, args.repo);
668
701
  }
@@ -705,7 +738,7 @@ function main() {
705
738
  if (!sourceDir) continue;
706
739
  const destDir = path.join(args.repo, skillsDestRoot, skillName);
707
740
  assertWithin(args.repo, destDir, "write skill destination");
708
- copySkillTree(sourceDir, destDir, args.force);
741
+ copySkillTree(sourceDir, destDir, args.force, args.repo);
709
742
  console.log(`installed\tskill:${skillName}\t${platform}\t${path.relative(args.repo, destDir)}`);
710
743
  bundled += 1;
711
744
  }