aiwg 2026.7.12 → 2026.7.13
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/README.md +3 -0
- package/agentic/code/frameworks/research-complete/lint/citation-resolves.yaml +2 -3
- package/agentic/code/frameworks/research-complete/skills/research-lint/SKILL.md +10 -5
- package/agentic/code/frameworks/sdlc-complete/templates/openhuman/AGENTS.md.aiwg-template +10 -9
- package/dist/src/lint/runner.js +62 -20
- package/docs/cli-reference.md +2 -1
- package/docs/contributing/ci-cd-secrets.md +81 -168
- package/docs/contributing/secret-rotation.md +24 -49
- package/docs/contributing/versioning.md +33 -32
- package/docs/customization/user-scope-deployment.md +4 -3
- package/docs/getting-started/audit-existing-code.md +6 -0
- package/docs/getting-started/just-try-it.md +14 -2
- package/docs/how-it-works.md +2 -2
- package/docs/integrations/claude-code-quickstart.md +5 -11
- package/docs/integrations/codex-quickstart.md +3 -3
- package/docs/integrations/copilot-quickstart.md +3 -3
- package/docs/integrations/cursor-quickstart.md +3 -3
- package/docs/integrations/factory-quickstart.md +3 -3
- package/docs/integrations/openhuman-quickstart.md +6 -1
- package/docs/integrations/warp-terminal-quickstart.md +3 -3
- package/docs/integrations/windsurf-quickstart.md +10 -7
- package/docs/overrides/README.md +43 -0
- package/docs/overrides/styles.css +129 -0
- package/docs/quickstart-mmk.md +2 -2
- package/docs/quickstart-sdlc.md +5 -9
- package/docs/quickstart.md +1 -1
- package/docs/regenerate-guide.md +15 -4
- package/docs/releases/_manifest.json +1 -0
- package/docs/releases/v2026.6.13-announcement.md +6 -0
- package/docs/releases/v2026.7.13-announcement.md +38 -0
- package/package.json +6 -6
- package/prebuilt/fortemi-core/framework/aiwg-fortemi-index-v2.json +1 -1
- package/prebuilt/fortemi-core/framework/manifest.json +2 -2
- package/tools/agents/providers/base.mjs +9 -3
- package/tools/agents/providers/openhuman.mjs +14 -1
package/README.md
CHANGED
|
@@ -499,6 +499,9 @@ aiwg use research # Research workflow automation (8 agents, 8-stage pip
|
|
|
499
499
|
aiwg use rlm # RLM addon (recursive context decomposition)
|
|
500
500
|
aiwg use all # Everything
|
|
501
501
|
|
|
502
|
+
# Existing projects: re-analyze and refresh the provider context bridge
|
|
503
|
+
aiwg regenerate
|
|
504
|
+
|
|
502
505
|
# Or scaffold a new project
|
|
503
506
|
aiwg new my-project
|
|
504
507
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
id: research/citation-resolves
|
|
2
2
|
name: Citation reference integrity
|
|
3
|
-
description: Every REF
|
|
3
|
+
description: Every REF reference in body text points to an existing note in the lint target
|
|
4
4
|
severity: error
|
|
5
5
|
applies-to:
|
|
6
6
|
glob: "**/*.md"
|
|
7
7
|
checks:
|
|
8
8
|
- type: reference-resolves
|
|
9
|
-
referencePattern: "
|
|
10
|
-
basePath: findings
|
|
9
|
+
referencePattern: "\\bREF-\\d{3,}\\b"
|
|
@@ -12,7 +12,7 @@ commandHint:
|
|
|
12
12
|
|
|
13
13
|
# Research Lint
|
|
14
14
|
|
|
15
|
-
Run the research corpus lint ruleset against
|
|
15
|
+
Run the research corpus lint ruleset against the configured corpus to detect structural and referential integrity issues.
|
|
16
16
|
|
|
17
17
|
## Triggers
|
|
18
18
|
|
|
@@ -26,7 +26,12 @@ Run the research corpus lint ruleset against `.aiwg/research/` to detect structu
|
|
|
26
26
|
## Parameters
|
|
27
27
|
|
|
28
28
|
### `[target]` (optional)
|
|
29
|
-
Path to lint.
|
|
29
|
+
Path to lint. When omitted, resolve the target in this order:
|
|
30
|
+
|
|
31
|
+
1. `<AIWG_CORPUS_ROOT>/documentation/references/` when `AIWG_CORPUS_ROOT` is set.
|
|
32
|
+
2. `<research.corpusRoot>/documentation/references/` when `.aiwg/aiwg.config` configures a corpus root.
|
|
33
|
+
3. `documentation/references/` when the canonical warehouse layout exists in the project.
|
|
34
|
+
4. `.aiwg/research/` for legacy project-local corpora.
|
|
30
35
|
|
|
31
36
|
### `--fix` (optional)
|
|
32
37
|
Attempt auto-fixes for fixable issues (add missing frontmatter defaults, correct formatting).
|
|
@@ -44,10 +49,10 @@ Severity threshold for failure: `error` (default), `warn`, or `info`.
|
|
|
44
49
|
|
|
45
50
|
### Phase 1: Run Lint
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
Resolve the default target as described above, then execute the lint runner against the research corpus:
|
|
48
53
|
|
|
49
54
|
```bash
|
|
50
|
-
aiwg lint
|
|
55
|
+
aiwg lint <resolved-target> --ruleset research --format full
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
This checks all 11 rules in the research ruleset:
|
|
@@ -57,7 +62,7 @@ This checks all 11 rules in the research ruleset:
|
|
|
57
62
|
| `ref-frontmatter` | error | Required frontmatter fields present |
|
|
58
63
|
| `ref-id-unique` | error | No duplicate REF-XXX identifiers |
|
|
59
64
|
| `ref-id-format` | warn | REF identifiers follow `REF-NNN` naming |
|
|
60
|
-
| `citation-resolves` | error | REF
|
|
65
|
+
| `citation-resolves` | error | Whole REF IDs point to exact or slugged notes anywhere in the lint target |
|
|
61
66
|
| `grade-present` | warn | GRADE quality assessment in frontmatter |
|
|
62
67
|
| `provenance-present` | warn | Provenance metadata present |
|
|
63
68
|
| `cross-ref-bidirectional` | info | Related refs linked both ways |
|
|
@@ -23,8 +23,8 @@ npm run lint
|
|
|
23
23
|
## AIWG SDLC Framework
|
|
24
24
|
|
|
25
25
|
This project uses [AIWG](https://aiwg.io) for SDLC orchestration. OpenHuman is
|
|
26
|
-
AIWG-convention-aware:
|
|
27
|
-
|
|
26
|
+
AIWG-convention-aware: kernel skills live in `~/.openhuman/skills/`, rule bodies
|
|
27
|
+
in `~/.openhuman/.aiwg/rules/`, and this file is the Discover-First bridge.
|
|
28
28
|
|
|
29
29
|
### Discover-First Protocol
|
|
30
30
|
|
|
@@ -41,17 +41,18 @@ corpus, not just the kernel set surfaced here.
|
|
|
41
41
|
|
|
42
42
|
### Skills
|
|
43
43
|
|
|
44
|
-
Kernel skills deploy to
|
|
45
|
-
scan root)
|
|
46
|
-
|
|
44
|
+
Kernel skills deploy to `~/.openhuman/skills/` (OpenHuman's user-scope native
|
|
45
|
+
scan root). Standard skills stay in AIWG's index-driven discovery system rather
|
|
46
|
+
than being copied into an OpenHuman scan root. Redeploy or refresh with:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
aiwg use sdlc --provider openhuman
|
|
50
50
|
aiwg refresh --provider openhuman
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
The default deploy emits no markdown persona copies. Curated OpenHuman-native
|
|
54
|
+
TOML workers are optional via `--harness-agents` and land under
|
|
55
|
+
`~/.openhuman/agents/`.
|
|
55
56
|
|
|
56
57
|
### Commands & Rules
|
|
57
58
|
|
|
@@ -59,8 +60,8 @@ OpenHuman has no native command surface; AIWG commands surface through this
|
|
|
59
60
|
`AGENTS.md` bridge and the deployed command-skills. Rule full bodies are on
|
|
60
61
|
disk under `.openhuman/.aiwg/rules/` and reachable via `aiwg show rule <name>`.
|
|
61
62
|
|
|
62
|
-
CRITICAL/HIGH rules are
|
|
63
|
-
|
|
63
|
+
CRITICAL/HIGH rules are rendered into the active context. The MEDIUM/LOW tier
|
|
64
|
+
is on-demand only — listed below, fetched via `aiwg show rule <name>`.
|
|
64
65
|
|
|
65
66
|
{{ON_DEMAND_RULES}}
|
|
66
67
|
|
package/dist/src/lint/runner.js
CHANGED
|
@@ -28,10 +28,56 @@ function parseFrontmatter(content) {
|
|
|
28
28
|
}
|
|
29
29
|
return result;
|
|
30
30
|
}
|
|
31
|
+
const DEFAULT_REFERENCE_PATTERN = '\\bREF-\\d{3,}\\b';
|
|
32
|
+
/**
|
|
33
|
+
* Build a target-wide artifact ID index once per lint run.
|
|
34
|
+
*
|
|
35
|
+
* Reference documents may use either an exact filename (`REF-001.md`) or the
|
|
36
|
+
* canonical slugged form (`REF-001-some-title.md`). Indexing the ID prefix also
|
|
37
|
+
* makes resolution independent of a particular corpus directory layout.
|
|
38
|
+
*/
|
|
39
|
+
function buildReferenceIndex(allFiles) {
|
|
40
|
+
const references = new Set();
|
|
41
|
+
for (const file of allFiles) {
|
|
42
|
+
if (path.extname(file).toLowerCase() !== '.md')
|
|
43
|
+
continue;
|
|
44
|
+
const basename = path.basename(file, '.md');
|
|
45
|
+
if (/^REF-\d+-(?:citations|radar)$/.test(basename))
|
|
46
|
+
continue;
|
|
47
|
+
references.add(basename);
|
|
48
|
+
const artifactId = basename.match(/^(.+?-\d+)(?:-|$)/);
|
|
49
|
+
if (artifactId)
|
|
50
|
+
references.add(artifactId[1]);
|
|
51
|
+
}
|
|
52
|
+
return references;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Preserve support for references stored outside the lint target when a rule
|
|
56
|
+
* explicitly supplies basePath. Accept both exact and slugged filenames.
|
|
57
|
+
*/
|
|
58
|
+
function resolvesFromBasePath(refId, targetDir, basePath) {
|
|
59
|
+
const candidateDirs = new Set([
|
|
60
|
+
path.resolve(targetDir, basePath),
|
|
61
|
+
path.resolve(process.cwd(), basePath),
|
|
62
|
+
]);
|
|
63
|
+
for (const dir of candidateDirs) {
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(path.join(dir, `${refId}.md`)))
|
|
66
|
+
return true;
|
|
67
|
+
if (fs.readdirSync(dir).some(file => file.startsWith(`${refId}-`) && file.endsWith('.md'))) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Missing or unreadable candidate directories do not resolve the ID.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
31
77
|
/**
|
|
32
78
|
* Run a single check against a file's content and frontmatter
|
|
33
79
|
*/
|
|
34
|
-
function runCheck(check, content, frontmatter, filePath, targetDir, allFiles) {
|
|
80
|
+
function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, referenceIndex) {
|
|
35
81
|
const diagnostics = [];
|
|
36
82
|
switch (check.type) {
|
|
37
83
|
case 'frontmatter-required': {
|
|
@@ -68,7 +114,7 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles) {
|
|
|
68
114
|
break;
|
|
69
115
|
}
|
|
70
116
|
case 'reference-resolves': {
|
|
71
|
-
const refPattern = check.referencePattern ||
|
|
117
|
+
const refPattern = check.referencePattern || DEFAULT_REFERENCE_PATTERN;
|
|
72
118
|
const regex = new RegExp(refPattern, 'g');
|
|
73
119
|
const lines = content.split('\n');
|
|
74
120
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -76,20 +122,15 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles) {
|
|
|
76
122
|
while ((match = regex.exec(lines[i])) !== null) {
|
|
77
123
|
const refId = match[0];
|
|
78
124
|
const basePath = check.basePath || '.aiwg/research/findings';
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
file: filePath,
|
|
89
|
-
message: `Reference '${refId}' does not resolve to an existing file`,
|
|
90
|
-
line: i + 1,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
125
|
+
if (!referenceIndex.has(refId) && !resolvesFromBasePath(refId, targetDir, basePath)) {
|
|
126
|
+
diagnostics.push({
|
|
127
|
+
ruleId: '',
|
|
128
|
+
ruleName: '',
|
|
129
|
+
severity: 'error',
|
|
130
|
+
file: filePath,
|
|
131
|
+
message: `Reference '${refId}' does not resolve to an existing file`,
|
|
132
|
+
line: i + 1,
|
|
133
|
+
});
|
|
93
134
|
}
|
|
94
135
|
}
|
|
95
136
|
}
|
|
@@ -149,7 +190,7 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles) {
|
|
|
149
190
|
break;
|
|
150
191
|
}
|
|
151
192
|
case 'cross-ref-bidirectional': {
|
|
152
|
-
const refPattern = check.referencePattern ||
|
|
193
|
+
const refPattern = check.referencePattern || DEFAULT_REFERENCE_PATTERN;
|
|
153
194
|
const regex = new RegExp(refPattern, 'g');
|
|
154
195
|
const currentBasename = path.basename(filePath, '.md');
|
|
155
196
|
const matches = content.match(regex) || [];
|
|
@@ -185,7 +226,7 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles) {
|
|
|
185
226
|
/**
|
|
186
227
|
* Run a single rule against all matching files in the target
|
|
187
228
|
*/
|
|
188
|
-
async function runRule(rule, targetDir, allFiles) {
|
|
229
|
+
async function runRule(rule, targetDir, allFiles, referenceIndex) {
|
|
189
230
|
const diagnostics = [];
|
|
190
231
|
// Filter files matching the rule's glob
|
|
191
232
|
const ruleGlob = rule.appliesTo.glob;
|
|
@@ -226,7 +267,7 @@ async function runRule(rule, targetDir, allFiles) {
|
|
|
226
267
|
for (const check of rule.checks) {
|
|
227
268
|
if (check.type === 'id-unique')
|
|
228
269
|
continue; // Already handled
|
|
229
|
-
const checkDiagnostics = runCheck(check, content, frontmatter, file, targetDir, allFiles);
|
|
270
|
+
const checkDiagnostics = runCheck(check, content, frontmatter, file, targetDir, allFiles, referenceIndex);
|
|
230
271
|
for (const d of checkDiagnostics) {
|
|
231
272
|
d.ruleId = rule.id;
|
|
232
273
|
d.ruleName = rule.name;
|
|
@@ -286,10 +327,11 @@ function autoDetectRulesets(target, available) {
|
|
|
286
327
|
export async function runLint(targetDir, rulesets, options = {}) {
|
|
287
328
|
const recursive = options.recursive ?? true;
|
|
288
329
|
const allFiles = await collectFiles(targetDir, recursive);
|
|
330
|
+
const referenceIndex = buildReferenceIndex(allFiles);
|
|
289
331
|
const allDiagnostics = [];
|
|
290
332
|
for (const ruleset of rulesets) {
|
|
291
333
|
for (const rule of ruleset.rules) {
|
|
292
|
-
const diagnostics = await runRule(rule, targetDir, allFiles);
|
|
334
|
+
const diagnostics = await runRule(rule, targetDir, allFiles, referenceIndex);
|
|
293
335
|
allDiagnostics.push(...diagnostics);
|
|
294
336
|
}
|
|
295
337
|
}
|
package/docs/cli-reference.md
CHANGED
|
@@ -461,7 +461,7 @@ aiwg use sdlc --ci-hooks-enabled --dry-run
|
|
|
461
461
|
| OpenCode | `opencode` | `.opencode/agent/`, `.opencode/commands/`, `.opencode/skill/`, `.opencode/rule/` | — |
|
|
462
462
|
| Hermes | `hermes` | `~/.hermes/skills/`, `AGENTS.md` (lean) | — |
|
|
463
463
|
| OpenClaw | `openclaw` | `~/.openclaw/agents/`, `~/.openclaw/commands/`, `~/.openclaw/skills/`, `~/.openclaw/rules/`, `~/.openclaw/behaviors/` | ✓ |
|
|
464
|
-
| OpenHuman | `openhuman` |
|
|
464
|
+
| OpenHuman | `openhuman` | `~/.openhuman/skills/`, `~/.openhuman/.aiwg/rules/`, optional `~/.openhuman/agents/aiwg_*.toml`, project `AGENTS.md` | — |
|
|
465
465
|
| Local/Ollama | `local` | Same as `claude` (local model, Claude Code paths) | — |
|
|
466
466
|
|
|
467
467
|
**Commands → Skills migration:**
|
|
@@ -473,6 +473,7 @@ On first run after the commands-to-skills migration, `aiwg use` detects an exist
|
|
|
473
473
|
- **Codex**: Commands and skills deploy to `~` (user-level) for availability across all projects; the provider ID is `codex`, not `openai`
|
|
474
474
|
- **Windsurf**: Agents aggregated into `AGENTS.md` at project root; no separate agent files
|
|
475
475
|
- **Warp**: Agents and commands also aggregated into `WARP.md` for single-file context loading
|
|
476
|
+
- **OpenHuman**: Kernel skills and rule bodies are user-global; the default deploy emits no markdown persona copies. Project context is rendered into `AGENTS.md`, and curated native TOML agents are opt-in with `--harness-agents`.
|
|
476
477
|
- **Hermes**: Not a spawnable CLI — access via `ollama run hermes3` or MCP sidecar; deploy sets up skills and a lean AGENTS.md
|
|
477
478
|
- **OpenClaw**: Only provider with behaviors support (`~/.openclaw/behaviors/`); all artifacts deploy to home directory
|
|
478
479
|
- **Local/Ollama**: Uses Claude Code path layout; specify `--coding-model ollama/<model>` to route coding tasks to the local model
|
|
@@ -1,240 +1,153 @@
|
|
|
1
1
|
# CI/CD Secrets Configuration
|
|
2
2
|
|
|
3
|
-
**Version:**
|
|
4
|
-
**Last Updated:** 2026-07-
|
|
3
|
+
**Version:** 3.0
|
|
4
|
+
**Last Updated:** 2026-07-15
|
|
5
5
|
**Target Audience:** Repository maintainers and administrators
|
|
6
6
|
|
|
7
|
-
AIWG's Gitea CI/CD workflows use
|
|
8
|
-
Actions stores only the
|
|
7
|
+
AIWG's Gitea CI/CD workflows use vault for repository-managed secrets. Gitea
|
|
8
|
+
Actions stores only the vault bootstrap pair:
|
|
9
9
|
|
|
10
10
|
| Tracker secret | Purpose |
|
|
11
11
|
|---|---|
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
12
|
+
| `VAULT_CI_ROLE_ID` | AppRole role ID for the `ci-aiwg` reader. |
|
|
13
|
+
| `VAULT_CI_SECRET_ID` | AppRole secret ID for the `ci-aiwg` reader. |
|
|
14
14
|
|
|
15
15
|
Do not recreate legacy Gitea secrets such as `NPM_TOKEN`, `GH_ACCESS_TOKEN`,
|
|
16
|
-
`AIWG_IO_DISPATCH_TOKEN`, or `
|
|
17
|
-
are fetched at runtime with [`../../ci/
|
|
16
|
+
`AIWG_IO_DISPATCH_TOKEN`, or `DOCSITE_DEPLOY_KEY`. Those values live in vault
|
|
17
|
+
and are fetched at runtime with [`../../ci/vault-fetch.sh`](../../ci/vault-fetch.sh).
|
|
18
18
|
|
|
19
19
|
`secrets.GITHUB_TOKEN` is not part of this migration. It is a per-run token
|
|
20
20
|
issued by the CI system and is still used by PR-comment workflows.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Vault Fetch Specs
|
|
23
23
|
|
|
24
24
|
| Workflow | Spec | Exported value |
|
|
25
25
|
|---|---|---|
|
|
26
|
-
| `.gitea/workflows/npm-publish.yml` | `ci/
|
|
27
|
-
| `.gitea/workflows/gitea-release.yml` | `ci/
|
|
28
|
-
| `.gitea/workflows/upload-release-sigs.yml` | `ci/
|
|
29
|
-
| `.gitea/workflows/github-mirror.yml` | `ci/
|
|
30
|
-
| `.gitea/workflows/notify-site.yml` | `ci/
|
|
31
|
-
| `.gitea/workflows/docsite-deploy.yml` | `ci/
|
|
26
|
+
| `.gitea/workflows/npm-publish.yml` | `ci/vault-fetch.npm-publish.spec` | `NODE_AUTH_TOKEN` |
|
|
27
|
+
| `.gitea/workflows/gitea-release.yml` | `ci/vault-fetch.gitea-release.spec` | `GITEA_TOKEN` |
|
|
28
|
+
| `.gitea/workflows/upload-release-sigs.yml` | `ci/vault-fetch.upload-release-sigs.spec` | `GITEA_TOKEN` |
|
|
29
|
+
| `.gitea/workflows/github-mirror.yml` | `ci/vault-fetch.github-mirror.spec` | `GH_TOKEN` |
|
|
30
|
+
| `.gitea/workflows/notify-site.yml` | `ci/vault-fetch.notify-site.spec` | `AIWG_IO_DISPATCH_TOKEN` |
|
|
31
|
+
| `.gitea/workflows/docsite-deploy.yml` | `ci/vault-fetch.docsite-deploy.spec` | `DOCSITE_DEPLOY_KEY_FILE` |
|
|
32
|
+
|
|
33
|
+
The specs contain only `*_VAULT_PATH` and `*_VAULT_FIELD` placeholders. Concrete
|
|
34
|
+
vault paths, metadata paths, hostnames, and fields are stored as private Gitea
|
|
35
|
+
variables or in the private operations catalog.
|
|
32
36
|
|
|
33
37
|
Validate specs without reading live secrets:
|
|
34
38
|
|
|
35
39
|
```bash
|
|
36
|
-
for f in ci/
|
|
37
|
-
bash ci/
|
|
40
|
+
for f in ci/vault-fetch.*.spec; do
|
|
41
|
+
bash ci/vault-fetch.sh --spec "$f" --dry-run
|
|
38
42
|
done
|
|
39
43
|
```
|
|
40
44
|
|
|
41
|
-
## Classification
|
|
42
|
-
|
|
43
|
-
| Name | Class | Destination |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| `BAO_CI_ROLE_ID` | `BOOTSTRAP` | Gitea Actions secret |
|
|
46
|
-
| `BAO_CI_SECRET_ID` | `BOOTSTRAP` | Gitea Actions secret |
|
|
47
|
-
| `GITHUB_TOKEN` | CI-issued token | No storage; issued per run |
|
|
48
|
-
| `NPM_TOKEN` | `SECRET` | `kv_internal/ci/aiwg/gitea-npm-token` field `token` |
|
|
49
|
-
| `GH_ACCESS_TOKEN` | `SECRET` | `kv_internal/ci/aiwg/github-mirror-token` field `token` |
|
|
50
|
-
| `AIWG_IO_DISPATCH_TOKEN` | `SECRET` | `kv_internal/ci/aiwg/aiwg-io-dispatch-token` field `token` |
|
|
51
|
-
| `DEPLOY_SSH_KEY` | `SECRET` | `kv_internal/ci/shared/docs-deploy` field `private_key` |
|
|
52
|
-
| `DEPLOY_HOST` | `CONFIG` | Gitea Actions variable |
|
|
53
|
-
| `DEPLOY_PORT` | `CONFIG` | Gitea Actions variable |
|
|
54
|
-
| `DEPLOY_USER` | `CONFIG` | Gitea Actions variable |
|
|
55
|
-
| `DEPLOY_PATH` | `CONFIG` | Gitea Actions variable |
|
|
56
|
-
|
|
57
|
-
## AppRole and Path Plan
|
|
58
|
-
|
|
59
|
-
The reader AppRole is `ci-aiwg`. Its token TTL should stay short, with a
|
|
60
|
-
maximum TTL no longer than the release jobs need.
|
|
61
|
-
|
|
62
|
-
The machine-readable plan is [`../../ci/openbao-migration-plan.json`](../../ci/openbao-migration-plan.json).
|
|
63
|
-
The AppRole policy file is [`../../ci/openbao-ci-aiwg.hcl`](../../ci/openbao-ci-aiwg.hcl).
|
|
64
|
-
|
|
65
|
-
Required readable leaves:
|
|
66
|
-
|
|
67
|
-
```text
|
|
68
|
-
kv_internal/data/ci/aiwg/gitea-npm-token
|
|
69
|
-
kv_internal/data/ci/aiwg/github-mirror-token
|
|
70
|
-
kv_internal/data/ci/aiwg/aiwg-io-dispatch-token
|
|
71
|
-
kv_internal/data/ci/shared/docs-deploy
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Adjacent repository paths should deny access. Verify with the OpenBao operator
|
|
75
|
-
runbook in `itops/docs/security/ci-secret-migration.md`.
|
|
76
|
-
|
|
77
45
|
Run the local consistency gate:
|
|
78
46
|
|
|
79
47
|
```bash
|
|
80
|
-
npm run lint:
|
|
48
|
+
npm run lint:vault-migration
|
|
81
49
|
```
|
|
82
50
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npm run lint:openbao-migration -- --live
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
To induct leaf values from an approved local medium, prepare one file per leaf:
|
|
51
|
+
## Classification
|
|
90
52
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
53
|
+
| Name | Class | Destination |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `VAULT_CI_ROLE_ID` | `BOOTSTRAP` | Gitea Actions secret |
|
|
56
|
+
| `VAULT_CI_SECRET_ID` | `BOOTSTRAP` | Gitea Actions secret |
|
|
57
|
+
| `GITHUB_TOKEN` | CI-issued token | No storage; issued per run |
|
|
58
|
+
| `GT_NPM_TOKEN_VAULT_PATH`, `GT_NPM_TOKEN_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
59
|
+
| `GT_RELEASE_TOKEN_VAULT_PATH`, `GT_RELEASE_TOKEN_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
60
|
+
| `GH_MIRROR_TOKEN_VAULT_PATH`, `GH_MIRROR_TOKEN_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
61
|
+
| `AIWG_IO_DISPATCH_TOKEN_VAULT_PATH`, `AIWG_IO_DISPATCH_TOKEN_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
62
|
+
| `DOCSITE_DEPLOY_KEY_VAULT_PATH`, `DOCSITE_DEPLOY_KEY_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
63
|
+
| `RELEASE_SIGNING_KEY_VAULT_PATH`, `RELEASE_SIGNING_KEY_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
64
|
+
| `RELEASE_SIGNING_PASSPHRASE_VAULT_PATH`, `RELEASE_SIGNING_PASSPHRASE_VAULT_FIELD` | `ROUTE` | Gitea Actions variables |
|
|
65
|
+
| `VAULT_ADDR` | `ROUTE` | Gitea Actions variable |
|
|
66
|
+
| `DOCSITE_DEPLOY_HOST`, `DOCSITE_DEPLOY_PORT`, `DOCSITE_DEPLOY_USER`, `DOCSITE_DEPLOY_PATH` | `CONFIG` | Gitea Actions variables |
|
|
97
67
|
|
|
98
|
-
|
|
68
|
+
The machine-readable variable manifest is
|
|
69
|
+
[`../../ci/vault-migration-plan.json`](../../ci/vault-migration-plan.json).
|
|
99
70
|
|
|
100
|
-
|
|
101
|
-
npm run provision:openbao-migration -- --values-dir /path/to/approved/value-files
|
|
102
|
-
```
|
|
71
|
+
## Provisioning
|
|
103
72
|
|
|
104
|
-
|
|
73
|
+
Use the private operations catalog to create the scoped policy and AppRole. Keep
|
|
74
|
+
the concrete policy file outside this repository, then pass it to:
|
|
105
75
|
|
|
106
76
|
```bash
|
|
107
|
-
npm run provision:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
```hcl
|
|
113
|
-
path "kv_internal/data/ci/aiwg/*" {
|
|
114
|
-
capabilities = ["read"]
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
path "kv_internal/metadata/ci/aiwg/*" {
|
|
118
|
-
capabilities = ["read", "list"]
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
path "kv_internal/data/ci/shared/docs-deploy" {
|
|
122
|
-
capabilities = ["read"]
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
path "kv_internal/metadata/ci/shared/docs-deploy" {
|
|
126
|
-
capabilities = ["read"]
|
|
127
|
-
}
|
|
77
|
+
npm run provision:vault-approle -- --policy-file /path/to/private-policy.hcl
|
|
78
|
+
VAULT_ADMIN_TOKEN=<admin-token> npm run provision:vault-approle -- \
|
|
79
|
+
--policy-file /path/to/private-policy.hcl \
|
|
80
|
+
--apply
|
|
128
81
|
```
|
|
129
82
|
|
|
130
|
-
|
|
131
|
-
|
|
83
|
+
To induct or rotate values from an approved local medium, prepare one file per
|
|
84
|
+
route ID:
|
|
132
85
|
|
|
133
|
-
```
|
|
134
|
-
npm
|
|
135
|
-
|
|
86
|
+
```text
|
|
87
|
+
gitea-npm-token.value
|
|
88
|
+
gitea-release-token.value
|
|
89
|
+
github-mirror-token.value
|
|
90
|
+
aiwg-io-dispatch-token.value
|
|
91
|
+
docsite-deploy-key.value
|
|
92
|
+
release-signing-key.value
|
|
93
|
+
release-signing-passphrase.value
|
|
136
94
|
```
|
|
137
95
|
|
|
138
|
-
|
|
96
|
+
Preview the vault writes without printing paths:
|
|
139
97
|
|
|
140
98
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
token_ttl=5m \
|
|
145
|
-
token_max_ttl=15m \
|
|
146
|
-
secret_id_ttl=0
|
|
99
|
+
npm run provision:vault-migration -- \
|
|
100
|
+
--routing-env /path/to/private-routing.env \
|
|
101
|
+
--values-dir /path/to/approved-value-files
|
|
147
102
|
```
|
|
148
103
|
|
|
149
|
-
|
|
104
|
+
Apply only after confirming the private routing env and value files:
|
|
150
105
|
|
|
151
106
|
```bash
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
"$(bao read -field=role_id auth/approle/role/ci-aiwg/role-id)"
|
|
157
|
-
printf 'BAO_CI_SECRET_ID=%s\n' \
|
|
158
|
-
"$(bao write -f -field=secret_id auth/approle/role/ci-aiwg/secret-id)"
|
|
159
|
-
} > ~/.config/openbao/handoff/aiwg-ci.env
|
|
160
|
-
chmod 600 ~/.config/openbao/handoff/aiwg-ci.env
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Prepare the non-secret deploy variables in a separate env file:
|
|
164
|
-
|
|
165
|
-
```text
|
|
166
|
-
DEPLOY_HOST=docs-host.example.internal
|
|
167
|
-
DEPLOY_PORT=22
|
|
168
|
-
DEPLOY_USER=deploy
|
|
169
|
-
DEPLOY_PATH=/srv/docs/aiwg
|
|
107
|
+
npm run provision:vault-migration -- \
|
|
108
|
+
--routing-env /path/to/private-routing.env \
|
|
109
|
+
--values-dir /path/to/approved-value-files \
|
|
110
|
+
--apply
|
|
170
111
|
```
|
|
171
112
|
|
|
172
|
-
Validate the Gitea handoff without printing values:
|
|
113
|
+
Validate and apply the Gitea handoff without printing values:
|
|
173
114
|
|
|
174
115
|
```bash
|
|
175
|
-
npm run configure:gitea-
|
|
176
|
-
--bootstrap-env ~/.config/
|
|
177
|
-
--vars-env /path/to/
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Apply after `tea login` is configured for `git.integrolabs.net`:
|
|
116
|
+
npm run configure:gitea-vault -- \
|
|
117
|
+
--bootstrap-env ~/.config/vault/handoff/aiwg-ci.env \
|
|
118
|
+
--vars-env /path/to/private-routing-and-deploy.env
|
|
181
119
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
--
|
|
185
|
-
--vars-env /path/to/aiwg-deploy-vars.env \
|
|
120
|
+
npm run configure:gitea-vault -- \
|
|
121
|
+
--bootstrap-env ~/.config/vault/handoff/aiwg-ci.env \
|
|
122
|
+
--vars-env /path/to/private-routing-and-deploy.env \
|
|
186
123
|
--apply
|
|
187
124
|
```
|
|
188
125
|
|
|
189
126
|
## Docsite Deployment Config
|
|
190
127
|
|
|
191
|
-
The `Docsite Deploy` workflow publishes the AIWG documentation tenant
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
| Variable | Purpose |
|
|
196
|
-
|---|---|
|
|
197
|
-
| `DEPLOY_HOST` | Docs host name. |
|
|
198
|
-
| `DEPLOY_PORT` | SSH port. |
|
|
199
|
-
| `DEPLOY_USER` | SSH user. |
|
|
200
|
-
| `DEPLOY_PATH` | Root web path for the AIWG tenant on `docs.aiwg.io`. |
|
|
128
|
+
The `Docsite Deploy` workflow publishes the AIWG documentation tenant over SSH.
|
|
129
|
+
The private key is fetched from vault. Target connection details are Gitea
|
|
130
|
+
Actions variables and should not be hardcoded in workflow files.
|
|
201
131
|
|
|
202
132
|
`GT_ACCESS_TOKEN` is not used by AIWG's current docsite workflows. The publisher
|
|
203
133
|
is installed from npm as `@pagenary/publisher`; do not add a clone token back to
|
|
204
134
|
the docsite workflows unless the publisher source model changes again.
|
|
205
135
|
|
|
206
|
-
## Shared `docs.aiwg.io` Tenants
|
|
207
|
-
|
|
208
|
-
`docs.aiwg.io` is shared with sibling documentation tenants. AIWG owns the root
|
|
209
|
-
tenant at `DEPLOY_PATH`; `roctinam/agentic-sandbox` owns the
|
|
210
|
-
`agentic-sandbox/` subtree and should deploy to:
|
|
211
|
-
|
|
212
|
-
```text
|
|
213
|
-
${DEPLOY_PATH%/}/agentic-sandbox/
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
AIWG's root deploy keeps `rsync --delete` enabled, but `docsite-deploy.yml`
|
|
217
|
-
protects registered sibling tenants with rsync receiver-protect filters and a
|
|
218
|
-
dry-run deletion check. Keep the protected-subpath list in
|
|
219
|
-
`.gitea/workflows/docsite-deploy.yml` aligned with the tenant table in
|
|
220
|
-
`.gitea/workflows/README.md`.
|
|
221
|
-
|
|
222
136
|
## Troubleshooting
|
|
223
137
|
|
|
224
|
-
###
|
|
138
|
+
### Vault Bootstrap Missing
|
|
225
139
|
|
|
226
|
-
If `
|
|
140
|
+
If `VAULT_CI_ROLE_ID` or `VAULT_CI_SECRET_ID` is absent, secret-bearing Gitea jobs
|
|
227
141
|
skip instead of attempting a partial publish. Add or rotate the AppRole
|
|
228
142
|
bootstrap pair in Gitea Actions secrets.
|
|
229
143
|
|
|
230
144
|
### Fetch Fails with 403
|
|
231
145
|
|
|
232
146
|
The `ci-aiwg` AppRole is missing the required path policy or is trying to read
|
|
233
|
-
the wrong leaf. Compare the workflow
|
|
234
|
-
|
|
147
|
+
the wrong leaf. Compare the workflow spec placeholder names with the private
|
|
148
|
+
vault routing env and policy.
|
|
235
149
|
|
|
236
150
|
### Gitea Registry Publish Fails
|
|
237
151
|
|
|
238
|
-
Check
|
|
239
|
-
|
|
240
|
-
release API work, but it must remain OpenBao-only.
|
|
152
|
+
Check the private routing entry, token scope, and token freshness. The value
|
|
153
|
+
must remain vault-only.
|