@ccoalm/ccl-skills 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +80 -4
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +16 -4
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +13 -2
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +7 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +65 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +4 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +14 -14
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +40 -83
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +44 -37
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +8 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +3 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +31 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +74 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +103 -21
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +74 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_runtime.py +428 -0
- package/dist/assets/release.json +45 -30
- package/dist/claude-adapter.js +14 -7
- package/dist/codex-host.d.ts +1 -3
- package/dist/codex-host.js +6 -9
- package/dist/host-probe.d.ts +11 -0
- package/dist/host-probe.js +27 -0
- package/dist/opencode-adapter.js +24 -19
- package/dist/unified.js +11 -9
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
mkdirSync,
|
|
7
7
|
mkdtempSync,
|
|
8
8
|
readFileSync,
|
|
9
|
+
readdirSync,
|
|
9
10
|
realpathSync,
|
|
10
11
|
rmSync,
|
|
11
12
|
statSync,
|
|
@@ -337,6 +338,7 @@ export const CclSkills = async (context: {
|
|
|
337
338
|
const hooksRoot = runtimeRoot()
|
|
338
339
|
const parentSessions = new Map<string, string>()
|
|
339
340
|
const idleInFlight = new Set<string>()
|
|
341
|
+
const pendingSkills = new Map<string, string>()
|
|
340
342
|
let stateRoot: string | null = null
|
|
341
343
|
|
|
342
344
|
function ensureStateRoot() {
|
|
@@ -390,9 +392,67 @@ export const CclSkills = async (context: {
|
|
|
390
392
|
return typeof command === "string" && /\b(?:git\s+(?:push|merge)|gh\b[^\n;&|]*\bpr\s+merge|glab\b[^\n;&|]*\bmr\s+(?:merge|accept)|curl|wget)\b/i.test(command)
|
|
391
393
|
}
|
|
392
394
|
|
|
393
|
-
function
|
|
394
|
-
if (
|
|
395
|
-
|
|
395
|
+
function sameSkillDirectory(loaded: string, active: string) {
|
|
396
|
+
if (loaded === realpathSync(active)) return true
|
|
397
|
+
const pending = [[loaded, active]]
|
|
398
|
+
while (pending.length) {
|
|
399
|
+
const [copy, owner] = pending.pop()!
|
|
400
|
+
const copyStat = lstatSync(copy), ownerStat = lstatSync(owner)
|
|
401
|
+
if (copyStat.isSymbolicLink() || ownerStat.isSymbolicLink()) return false
|
|
402
|
+
if (copyStat.isDirectory() && ownerStat.isDirectory()) {
|
|
403
|
+
const copyNames = readdirSync(copy).sort(), ownerNames = readdirSync(owner).sort()
|
|
404
|
+
if (copyNames.length !== ownerNames.length || copyNames.some((name, index) => name !== ownerNames[index])) return false
|
|
405
|
+
for (const name of ownerNames) pending.push([join(copy, name), join(owner, name)])
|
|
406
|
+
} else if (!copyStat.isFile() || !ownerStat.isFile() || copyStat.size !== ownerStat.size || !readFileSync(copy).equals(readFileSync(owner))) return false
|
|
407
|
+
}
|
|
408
|
+
return true
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function completedSkill(name: string | undefined, metadata: unknown) {
|
|
412
|
+
if (!name || !hooksRoot || !metadata || typeof metadata !== "object") return null
|
|
413
|
+
const result = metadata as { name?: unknown; dir?: unknown }
|
|
414
|
+
if (result.name !== name || typeof result.dir !== "string") return null
|
|
415
|
+
// The native tool reports the loaded source directory. A same-named skill
|
|
416
|
+
// elsewhere must never be promoted to a CCL owner by its caller's argument.
|
|
417
|
+
try {
|
|
418
|
+
const loaded = realpathSync(result.dir)
|
|
419
|
+
const entry = join(loaded, "SKILL.md")
|
|
420
|
+
if (!lstatSync(entry).isFile() || lstatSync(entry).isSymbolicLink()) return null
|
|
421
|
+
const content = readFileSync(entry, "utf8")
|
|
422
|
+
if (!content.startsWith(`---\nname: ${name}\n`)) return null
|
|
423
|
+
const activeRoot = existsSync(join(hooksRoot, "skills")) ? hooksRoot : resolve(hooksRoot, "../..")
|
|
424
|
+
// Bind the full skill to the running runtime: progressive disclosure loads
|
|
425
|
+
// references/scripts relative to this directory. Active source/project edits
|
|
426
|
+
// remain valid; inactive copies must match current files, without a stale cache.
|
|
427
|
+
const activeSkill = join(activeRoot, "skills", name)
|
|
428
|
+
const activeEntry = join(activeSkill, "SKILL.md")
|
|
429
|
+
if (!existsSync(activeEntry) || !lstatSync(activeEntry).isFile() || lstatSync(activeEntry).isSymbolicLink() || !sameSkillDirectory(loaded, activeSkill)) return null
|
|
430
|
+
const nativeRoots = [activeRoot, join(HOST_HOME, ".config/opencode"), join(directory, ".opencode"), join(context.worktree ?? directory, ".opencode")]
|
|
431
|
+
for (const root of nativeRoots) {
|
|
432
|
+
const expected = join(root, "skills", name)
|
|
433
|
+
if (existsSync(expected) && realpathSync(expected) === loaded && (root === activeRoot || existsSync(join(root, "ccl-skills/runtime/hooks/hooks.json")))) return `ccl-skills:${name}`
|
|
434
|
+
}
|
|
435
|
+
// Explicit skills.paths can select a CCL source checkout while a global
|
|
436
|
+
// adapter supplies the runtime. Require both its layout and the active bytes.
|
|
437
|
+
const sourceRoot = resolve(loaded, "../..")
|
|
438
|
+
const manifestPath = join(sourceRoot, ".claude-plugin/plugin.json")
|
|
439
|
+
if (loaded === join(sourceRoot, "skills", name) && existsSync(manifestPath)) {
|
|
440
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"))
|
|
441
|
+
if (manifest?.name === "ccl-skills" && manifest?.skills === "./skills/") return `ccl-skills:${name}`
|
|
442
|
+
}
|
|
443
|
+
// The source installer also supports ~/.agents/skills. Require its existing
|
|
444
|
+
// receipt and the current native skill's bytes, not just a same-named file.
|
|
445
|
+
const compatibility = join(HOST_HOME, ".agents/skills", name)
|
|
446
|
+
const native = join(HOST_HOME, ".config/opencode/skills", name, "SKILL.md")
|
|
447
|
+
const receipt = join(HOST_HOME, ".config/opencode/ccl-skills/install-manifest.json")
|
|
448
|
+
if (existsSync(compatibility) && realpathSync(compatibility) === loaded && existsSync(receipt) && existsSync(native)) {
|
|
449
|
+
const manifest = JSON.parse(readFileSync(receipt, "utf8"))
|
|
450
|
+
if (manifest?.installer === "scripts/install-opencode.sh" && manifest?.install_mode === "global" && readFileSync(native, "utf8") === content) return `ccl-skills:${name}`
|
|
451
|
+
}
|
|
452
|
+
return null
|
|
453
|
+
} catch {
|
|
454
|
+
return null
|
|
455
|
+
}
|
|
396
456
|
}
|
|
397
457
|
|
|
398
458
|
async function resumeForStop(sessionID: string, reasons: string[]) {
|
|
@@ -446,6 +506,9 @@ export const CclSkills = async (context: {
|
|
|
446
506
|
output.args = args
|
|
447
507
|
const targets = editPaths(tool, args, directory)
|
|
448
508
|
const toolName = claudeToolName(tool)
|
|
509
|
+
if (tool === "skill" && typeof args.name === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(args.name) && args.name.length <= 64) {
|
|
510
|
+
pendingSkills.set(`${sessionID}\0${callID}`, args.name)
|
|
511
|
+
}
|
|
449
512
|
if (targets.length) {
|
|
450
513
|
targets.forEach((filePath, index) => appendTranscript(sessionID, {
|
|
451
514
|
type: "assistant",
|
|
@@ -454,7 +517,7 @@ export const CclSkills = async (context: {
|
|
|
454
517
|
} else {
|
|
455
518
|
appendTranscript(sessionID, {
|
|
456
519
|
type: "assistant",
|
|
457
|
-
message: { content: [{ type: "tool_use", id: callID, name: toolName, input:
|
|
520
|
+
message: { content: [{ type: "tool_use", id: callID, name: toolName, input: {} }] },
|
|
458
521
|
})
|
|
459
522
|
}
|
|
460
523
|
|
|
@@ -507,6 +570,15 @@ export const CclSkills = async (context: {
|
|
|
507
570
|
const tool = input.tool.toLowerCase()
|
|
508
571
|
const sessionID = input.sessionID ?? `pid-${process.ppid}`
|
|
509
572
|
const callID = input.callID ?? "unknown-call"
|
|
573
|
+
if (tool === "skill") {
|
|
574
|
+
const key = `${sessionID}\0${callID}`
|
|
575
|
+
const owner = completedSkill(pendingSkills.get(key), output.metadata)
|
|
576
|
+
pendingSkills.delete(key)
|
|
577
|
+
if (owner) appendTranscript(sessionID, {
|
|
578
|
+
type: "assistant",
|
|
579
|
+
message: { content: [{ type: "tool_use", id: callID, name: "Skill", input: { skill: owner } }] },
|
|
580
|
+
})
|
|
581
|
+
}
|
|
510
582
|
appendTranscript(sessionID, {
|
|
511
583
|
type: "user",
|
|
512
584
|
message: { content: [{ type: "tool_result", tool_use_id: callID, content: "" }] },
|
|
@@ -536,6 +608,9 @@ export const CclSkills = async (context: {
|
|
|
536
608
|
? (properties.info as { id: string }).id
|
|
537
609
|
: ""
|
|
538
610
|
if (!sessionID) return
|
|
611
|
+
if (event.type === "session.deleted" || event.type === "session.idle" || (properties.status as { type?: string } | undefined)?.type === "idle") {
|
|
612
|
+
for (const key of pendingSkills.keys()) if (key.startsWith(`${sessionID}\0`)) pendingSkills.delete(key)
|
|
613
|
+
}
|
|
539
614
|
if (event.type === "session.deleted") {
|
|
540
615
|
const path = transcriptPath(sessionID)
|
|
541
616
|
if (path) rmSync(path, { force: true })
|
|
@@ -562,6 +637,7 @@ export const CclSkills = async (context: {
|
|
|
562
637
|
},
|
|
563
638
|
|
|
564
639
|
dispose: async () => {
|
|
640
|
+
pendingSkills.clear()
|
|
565
641
|
if (stateRoot) rmSync(stateRoot, { recursive: true, force: true })
|
|
566
642
|
stateRoot = null
|
|
567
643
|
},
|
|
@@ -3,12 +3,24 @@ description: Install or refresh CCL skills for OpenCode
|
|
|
3
3
|
argument-hint: "[--project]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Install or reapply the CCL assets using the current installation mode. Check the existing CCL installation record and available entrypoint before running a command.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
For an npm installation, reapply the invoked package's OpenCode assets:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
|
|
11
|
+
ccl-skills install --host opencode
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
For a newer package version, follow `/ccl-update-skills`. The npm package does not include the source installer or support `--project`; do not forward that flag to the npm CLI.
|
|
15
|
+
|
|
16
|
+
For a source checkout, locate the actual CCL repository and verify its installer exists. By default, from that checkout run:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bash scripts/install-opencode.sh --no-agent $ARGUMENTS
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use `--project` only for source mode when the user wants `.opencode/skills`, `.opencode/commands`, and `.opencode/plugins` copied into that checkout. Do not run a relative source-installer path in an unrelated project.
|
|
23
|
+
|
|
24
|
+
For a global source installation, omit `--no-agent` when the user also needs `~/.agents/skills` for another tool. To sync only that compatibility path, run `bash scripts/install-opencode.sh --only-agent`; this mode does not install OpenCode assets and cannot be combined with `--project` or `--no-agent`.
|
|
25
|
+
|
|
26
|
+
After OpenCode assets change, restart OpenCode or open a new session. After a compatibility-only sync, restart the tool that reads `~/.agents/skills`.
|
|
@@ -1059,7 +1059,15 @@ def main(argv):
|
|
|
1059
1059
|
head_enabled = enabled(head_cfg)
|
|
1060
1060
|
base_enabled = enabled(base_cfg)
|
|
1061
1061
|
|
|
1062
|
-
|
|
1062
|
+
# --no-renames so a file moved OUT of a gated directory is still seen at its old
|
|
1063
|
+
# (gated) path; rename detection would only report the exempt destination.
|
|
1064
|
+
# --ignore-submodules=none so repository config (diff.ignoreSubmodules=all) cannot
|
|
1065
|
+
# suppress a gitlink move out of the gated tree, which would leave only .gitmodules.
|
|
1066
|
+
diff = run_git(
|
|
1067
|
+
["diff", "-z", "--name-only", "--no-renames", "--ignore-submodules=none", base, "HEAD"],
|
|
1068
|
+
cwd=repo,
|
|
1069
|
+
text=False,
|
|
1070
|
+
)
|
|
1063
1071
|
if diff.returncode != 0:
|
|
1064
1072
|
return fail("owner-dispatch ci: diff failed — FAIL-CLOSED (2)")
|
|
1065
1073
|
changed = [p.decode("utf-8", "surrogateescape") for p in diff.stdout.split(b"\0") if p]
|
|
@@ -1183,7 +1191,10 @@ cmd_ci() {
|
|
|
1183
1191
|
# file (NUL can't survive a shell var) so paths with spaces/newlines classify exactly.
|
|
1184
1192
|
local CHANGED=() difftmp rel
|
|
1185
1193
|
difftmp=$(mktemp 2>/dev/null) || { echo "owner-dispatch ci: mktemp failed — FAIL-CLOSED (2)" >&2; return 2; }
|
|
1186
|
-
|
|
1194
|
+
# -C "$repo" so diff.relative cannot truncate the set when ci runs from a subdirectory;
|
|
1195
|
+
# --no-renames and --ignore-submodules=none: see the Python path above. Both paths must
|
|
1196
|
+
# classify the same file set regardless of repository diff configuration or cwd.
|
|
1197
|
+
git -C "$repo" diff -z --name-only --no-renames --ignore-submodules=none "$base" HEAD > "$difftmp" 2>/dev/null || { rm -f "$difftmp"; echo "owner-dispatch ci: diff failed — FAIL-CLOSED (2)" >&2; return 2; }
|
|
1187
1198
|
while IFS= read -r -d '' rel; do CHANGED+=("$rel"); done < "$difftmp"
|
|
1188
1199
|
rm -f "$difftmp"
|
|
1189
1200
|
|
|
@@ -523,6 +523,59 @@ echo "// z" >> "$REPO/src/foo.go"; gc more
|
|
|
523
523
|
# 10i. CI fails CLOSED when it cannot determine a base (no --base, no upstream).
|
|
524
524
|
if ( cd "$REPO" && bash "$ENGINE" ci >/dev/null 2>&1 ); then bad "ci no-base should fail-closed"; else ok "ci: no base => fail-closed (non-zero)"; fi
|
|
525
525
|
|
|
526
|
+
# 10j. a gated file moved OUT of the gated tree is still a gated change. Two ways the diff
|
|
527
|
+
# can hide it: rename detection resolves a pure move to the exempt destination only,
|
|
528
|
+
# and diff.ignoreSubmodules=all drops a moved gitlink entirely. Both ci paths (jq and
|
|
529
|
+
# the python fallback) must classify the same set, so each case runs on both.
|
|
530
|
+
command -v jq >/dev/null 2>&1 || bad "ci rename-out: jq absent" "the jq lane would silently be a second python run"
|
|
531
|
+
[ -z "$(PATH="$NOJQBIN" command -v jq || true)" ] || bad "ci rename-out: NOJQBIN still resolves jq" "the python lane would silently be a second jq run"
|
|
532
|
+
printf '%s\n' "$EN" | rawcfg
|
|
533
|
+
mkdir -p "$REPO/design" "$REPO/exempt"; echo "owners: x" > "$REPO/design/map.md"
|
|
534
|
+
echo "package x" > "$REPO/src/moved.go"; gc renamebase
|
|
535
|
+
rnbase=$(git -C "$REPO" rev-parse HEAD)
|
|
536
|
+
git -C "$REPO" config diff.renames true # else the base engine reports the source anyway
|
|
537
|
+
git -C "$REPO" mv src/moved.go exempt/moved.go; gc renameout
|
|
538
|
+
jq_rc=0; jq_out=$( cd "$REPO" && bash "$ENGINE" ci --base "$rnbase" 2>&1 ) || jq_rc=$?
|
|
539
|
+
py_rc=0; py_out=$( cd "$REPO" && PATH="$NOJQBIN" bash "$ENGINE" ci --base "$rnbase" 2>&1 ) || py_rc=$?
|
|
540
|
+
[ "$jq_rc" = 1 ] && [ "$py_rc" = 1 ] && ok "ci: gated file renamed out of scope, stale map => fail on both lanes" || bad "ci rename-out rc" "jq=$jq_rc py=$py_rc"
|
|
541
|
+
case "$jq_out$py_out" in *src/moved.go*src/moved.go*) ok "ci rename-out: both lanes name the gated source path" ;; *) bad "ci rename-out diagnostic" "$jq_out | $py_out" ;; esac
|
|
542
|
+
# cwd must not change the verdict: with diff.relative=true a diff run from a subdirectory
|
|
543
|
+
# would drop paths outside it, and the two lanes would disagree.
|
|
544
|
+
git -C "$REPO" config diff.relative true
|
|
545
|
+
rel_jq=0; ( cd "$REPO/exempt" && bash "$ENGINE" ci --base "$rnbase" >/dev/null 2>&1 ) || rel_jq=$?
|
|
546
|
+
rel_py=0; ( cd "$REPO/exempt" && PATH="$NOJQBIN" bash "$ENGINE" ci --base "$rnbase" >/dev/null 2>&1 ) || rel_py=$?
|
|
547
|
+
[ "$rel_jq" = 1 ] && [ "$rel_py" = 1 ] && ok "ci: run from a subdirectory under diff.relative=true => still fail on both lanes" || bad "ci rename-out relative rc" "jq=$rel_jq py=$rel_py"
|
|
548
|
+
git -C "$REPO" config --unset diff.relative
|
|
549
|
+
echo "owners: y" >> "$REPO/design/map.md"; gc renameoutmap
|
|
550
|
+
ok_jq=0; ( cd "$REPO" && bash "$ENGINE" ci --base "$rnbase" >/dev/null 2>&1 ) || ok_jq=$?
|
|
551
|
+
ok_py=0; ( cd "$REPO" && PATH="$NOJQBIN" bash "$ENGINE" ci --base "$rnbase" >/dev/null 2>&1 ) || ok_py=$?
|
|
552
|
+
[ "$ok_jq" = 0 ] && [ "$ok_py" = 0 ] && ok "ci: renamed-out gated file with updated map => ok on both lanes" || bad "ci rename-out with map" "jq=$ok_jq py=$ok_py"
|
|
553
|
+
# an option-like base must be rejected before any diff runs, on both lanes
|
|
554
|
+
for badbase in "--output=$WORK/pwned" "-z"; do
|
|
555
|
+
b_jq=0; ( cd "$REPO" && bash "$ENGINE" ci --base "$badbase" >/dev/null 2>&1 ) || b_jq=$?
|
|
556
|
+
b_py=0; ( cd "$REPO" && PATH="$NOJQBIN" bash "$ENGINE" ci --base "$badbase" >/dev/null 2>&1 ) || b_py=$?
|
|
557
|
+
[ "$b_jq" = 2 ] && [ "$b_py" = 2 ] && ok "ci: option-like base '$badbase' => fail-closed(2) on both lanes" || bad "ci option-base rc" "jq=$b_jq py=$b_py"
|
|
558
|
+
done
|
|
559
|
+
[ ! -e "$WORK/pwned" ] && ok "ci: option-like base created no file" || bad "ci option-base wrote a file"
|
|
560
|
+
|
|
561
|
+
# 10j2. the same bypass through a submodule, which repository config can hide outright.
|
|
562
|
+
SUBSRC="$WORK/subsrc"; mkdir -p "$SUBSRC"
|
|
563
|
+
git -C "$SUBSRC" init -q; git -C "$SUBSRC" config user.email t@t; git -C "$SUBSRC" config user.name t
|
|
564
|
+
echo hi > "$SUBSRC/f.txt"; git -C "$SUBSRC" add -A; git -C "$SUBSRC" commit -qm sub
|
|
565
|
+
sub_err=$(git -C "$REPO" -c protocol.file.allow=always submodule add -q "$SUBSRC" src/vendor 2>&1) || true
|
|
566
|
+
if [ -e "$REPO/src/vendor/f.txt" ]; then
|
|
567
|
+
gc subbase
|
|
568
|
+
sub_base=$(git -C "$REPO" rev-parse HEAD)
|
|
569
|
+
git -C "$REPO" config diff.ignoreSubmodules all
|
|
570
|
+
git -C "$REPO" mv src/vendor exempt/vendor; gc submove
|
|
571
|
+
sm_jq=0; sm_jq_out=$( cd "$REPO" && bash "$ENGINE" ci --base "$sub_base" 2>&1 ) || sm_jq=$?
|
|
572
|
+
sm_py=0; sm_py_out=$( cd "$REPO" && PATH="$NOJQBIN" bash "$ENGINE" ci --base "$sub_base" 2>&1 ) || sm_py=$?
|
|
573
|
+
[ "$sm_jq" = 1 ] && [ "$sm_py" = 1 ] && ok "ci: submodule moved out of scope under diff.ignoreSubmodules=all => fail on both lanes" || bad "ci submodule-move rc" "jq=$sm_jq py=$sm_py"
|
|
574
|
+
git -C "$REPO" config --unset diff.ignoreSubmodules
|
|
575
|
+
else
|
|
576
|
+
bad "ci submodule-move: could not create the submodule fixture" "$sub_err"
|
|
577
|
+
fi
|
|
578
|
+
|
|
526
579
|
# ---- 11. SubagentStop: agent_id-scoped enforcement (the same engine handles Stop +
|
|
527
580
|
# SubagentStop; agent_id is present only for the latter and scopes markers/cap). ----
|
|
528
581
|
rm -rf "$BDIR"
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py
CHANGED
|
@@ -2736,6 +2736,7 @@ def freeze_review_profile(
|
|
|
2736
2736
|
required_self_review_fields = {"concern", "conclusion", "evidence_refs"}
|
|
2737
2737
|
self_review: list[dict[str, Any]] = []
|
|
2738
2738
|
seen_concerns: set[str] = set()
|
|
2739
|
+
seen_owner_concerns: set[tuple[str, str]] = set()
|
|
2739
2740
|
for index, item in enumerate(self_review_raw):
|
|
2740
2741
|
item_fields = set(item) if isinstance(item, dict) else set()
|
|
2741
2742
|
if item_fields not in (
|
|
@@ -2761,7 +2762,6 @@ def freeze_review_profile(
|
|
|
2761
2762
|
len(normalized_conclusion) < 20
|
|
2762
2763
|
or placeholder_key in PLACEHOLDER_TEXT
|
|
2763
2764
|
or concern not in known_concern_ids
|
|
2764
|
-
or concern in seen_concerns
|
|
2765
2765
|
or not isinstance(references, list)
|
|
2766
2766
|
or not references
|
|
2767
2767
|
or any(
|
|
@@ -2781,6 +2781,12 @@ def freeze_review_profile(
|
|
|
2781
2781
|
)
|
|
2782
2782
|
except GateError as exc:
|
|
2783
2783
|
raise GateError(exc.reason, "self_review_incomplete") from exc
|
|
2784
|
+
if (concern, skill) in seen_owner_concerns:
|
|
2785
|
+
raise GateError(
|
|
2786
|
+
"self_review repeats a concern for the same skill",
|
|
2787
|
+
"self_review_incomplete",
|
|
2788
|
+
)
|
|
2789
|
+
seen_owner_concerns.add((concern, skill))
|
|
2784
2790
|
seen_concerns.add(concern)
|
|
2785
2791
|
self_review.append(
|
|
2786
2792
|
{
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh
CHANGED
|
@@ -1233,6 +1233,71 @@ out="$(run_gate --review-plan-file "$WORK/owner-review-plan.json" --allow-fallba
|
|
|
1233
1233
|
check "owner-aware success without a wrapper binding receipt fails closed" \
|
|
1234
1234
|
'[ "$rc" = 2 ] && json_fields "$out" reason_code=binding_mismatch next_action=stop_reviewer_lane && [ "$(printf "%s" "$out" | python3 -c "import json,sys; print(len(json.load(sys.stdin).get(\"reviewed_skills\", [])))")" = 0 ]'
|
|
1235
1235
|
|
|
1236
|
+
# Owner count is independent of concern count: a cross-skill candidate can have
|
|
1237
|
+
# more owners than the eight known concerns without changing the review scope.
|
|
1238
|
+
python3 - "$WORK" <<'PY'
|
|
1239
|
+
import copy
|
|
1240
|
+
import json
|
|
1241
|
+
from pathlib import Path
|
|
1242
|
+
import sys
|
|
1243
|
+
|
|
1244
|
+
work = Path(sys.argv[1])
|
|
1245
|
+
owners = (
|
|
1246
|
+
"requirement-scope", "product-rd-workflow", "platform-service-connectivity",
|
|
1247
|
+
"platform-observability", "skill-extraction-workflow", "python-service-dev",
|
|
1248
|
+
"web-react-dev", "testing-strategy", "terminal-cli-dev",
|
|
1249
|
+
)
|
|
1250
|
+
plan = json.loads((work / "review-plan.json").read_text())
|
|
1251
|
+
patches = []
|
|
1252
|
+
for owner in owners:
|
|
1253
|
+
root = work / owner
|
|
1254
|
+
root.mkdir(exist_ok=True)
|
|
1255
|
+
entrypoint = root / "SKILL.md"
|
|
1256
|
+
if not entrypoint.exists():
|
|
1257
|
+
entrypoint.write_text(f"# Synthetic {owner} owner\n")
|
|
1258
|
+
plan["self_review"].append({
|
|
1259
|
+
"concern": "correctness", "skill": owner,
|
|
1260
|
+
"conclusion": f"The {owner} boundary preserves its stated acceptance behavior.",
|
|
1261
|
+
"evidence_refs": ["e1"],
|
|
1262
|
+
})
|
|
1263
|
+
path = f"skills/{owner}/SKILL.md"
|
|
1264
|
+
patches.append(f"diff --git a/{path} b/{path}\n--- a/{path}\n+++ b/{path}\n@@ -1 +1 @@\n-old\n+new\n")
|
|
1265
|
+
(work / "many-owner-diff.patch").write_text("".join(patches))
|
|
1266
|
+
plans = {"many-owner": plan}
|
|
1267
|
+
missing_owner = copy.deepcopy(plan)
|
|
1268
|
+
missing_owner["self_review"].pop()
|
|
1269
|
+
plans["many-owner-missing-owner"] = missing_owner
|
|
1270
|
+
missing_concern = copy.deepcopy(plan)
|
|
1271
|
+
missing_concern["self_review"] = [row for row in missing_concern["self_review"] if row["concern"] != "safety"]
|
|
1272
|
+
plans["many-owner-missing-concern"] = missing_concern
|
|
1273
|
+
duplicate = copy.deepcopy(plan)
|
|
1274
|
+
duplicate["self_review"].append(copy.deepcopy(duplicate["self_review"][-1]))
|
|
1275
|
+
plans["many-owner-duplicate"] = duplicate
|
|
1276
|
+
default_duplicate = copy.deepcopy(plan)
|
|
1277
|
+
default_duplicate["self_review"].append({**default_duplicate["self_review"][0], "skill": "code-review"})
|
|
1278
|
+
plans["many-owner-default-duplicate"] = default_duplicate
|
|
1279
|
+
for name, value in plans.items():
|
|
1280
|
+
(work / f"{name}-plan.json").write_text(json.dumps(value))
|
|
1281
|
+
PY
|
|
1282
|
+
|
|
1283
|
+
reset_case passed unavailable unavailable
|
|
1284
|
+
out="$(run_gate --diff-file "$WORK/many-owner-diff.patch" --review-plan-file "$WORK/many-owner-plan.json")"; rc=$?
|
|
1285
|
+
check "nine derived owners can assess shared concerns in one explicit plan" \
|
|
1286
|
+
'[ "$rc" = 0 ] && json_fields "$out" status=passed review_plan_source=implementer-supplied owner_selection_source=controller-derived+implementer-declared && [ "$(printf %s "$out" | python3 -c "import json,sys; p=json.load(sys.stdin); print(len(p[\"selected_skills\"]), len(p[\"reviewed_skills\"]))")" = "10 9" ]'
|
|
1287
|
+
printf '%s\n' "$out" >"$WORK/many-owner-review.json"
|
|
1288
|
+
|
|
1289
|
+
reset_case passed unavailable unavailable
|
|
1290
|
+
out="$(run_completion_gate --diff-file "$WORK/many-owner-diff.patch" --review-plan-file "$WORK/many-owner-plan.json" --completion-review-result-file "$WORK/many-owner-review.json")"; rc=$?
|
|
1291
|
+
check "nine-owner explicit self-review closes the exact-candidate completion checkpoint" \
|
|
1292
|
+
'[ "$rc" = 0 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" mode=complete status=passed completion_gated=false next_action=complete'
|
|
1293
|
+
|
|
1294
|
+
for invalid_plan in missing-owner missing-concern duplicate default-duplicate; do
|
|
1295
|
+
reset_case passed unavailable unavailable
|
|
1296
|
+
out="$(run_gate --diff-file "$WORK/many-owner-diff.patch" --review-plan-file "$WORK/many-owner-$invalid_plan-plan.json")"; rc=$?
|
|
1297
|
+
check "shared-concern owner plans reject $invalid_plan before provider execution" \
|
|
1298
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=self_review_incomplete next_action=deep_self_review'
|
|
1299
|
+
done
|
|
1300
|
+
|
|
1236
1301
|
printf 'diff --git a/skills/testing-strategy/SKILL.md b/skills/testing-strategy/SKILL.md\n--- a/skills/testing-strategy/SKILL.md\n+++ b/skills/testing-strategy/SKILL.md\n@@ -1 +1 @@\n-old\n+new\n' >"$WORK/diff.patch"
|
|
1237
1302
|
reset_case passed unavailable unavailable
|
|
1238
1303
|
out="$(run_gate --allow-fallback-egress)"; rc=$?
|
|
@@ -30,7 +30,7 @@ Observability is the chain that turns one user action into searchable, joinable,
|
|
|
30
30
|
2. **Instrumentation** — every service emits structured logs, metrics, and spans through the **framework default**, not ad-hoc code. A service whose middleware chain does not include `ctx_inject + metrics + recovery + tracing` is unobservable by design.
|
|
31
31
|
3. **Transport** — logs go stdout → file collector (DaemonSet) → log pipeline → search index; metrics + traces go SDK → OTLP collector → metric store + trace store. Both transports must survive collector restarts and back-pressure.
|
|
32
32
|
4. **Storage + display** — logs in a searchable index keyed by log-id and trace-id; metrics in a long-term store separate from scraper; traces queryable by trace-id; one dashboard tool joins all three.
|
|
33
|
-
5. **Evidence consumption** — SLIs
|
|
33
|
+
5. **Evidence consumption** — SLIs query (3) + (4); actionable alerts route to on-call; runbooks live in a wiki that on-call can reach in under one minute.
|
|
34
34
|
|
|
35
35
|
A new service must satisfy all five before it is allowed in production. A release must produce evidence at all five before it is promoted.
|
|
36
36
|
|
|
@@ -106,9 +106,9 @@ Add domain fields with a prefix (e.g. `app_*`, `biz_*`) to avoid colliding with
|
|
|
106
106
|
|
|
107
107
|
**Retrofit / migration contract** (the rules above are otherwise greenfield-framed): when a schema is introduced over EXISTING services, field names, metric label names, and identity env-var names are a **migration contract** — a blind rename breaks every deployed dashboard, alert, and saved query that keys on the old name. Retrofitting MUST alias or dual-write old→new and migrate consumers before retiring the old name; never rename in place. The cheap time to fix a name is before services adopt it.
|
|
108
108
|
|
|
109
|
-
### R7 — Alerts are
|
|
109
|
+
### R7 — Alerts are actionable and route to an owner
|
|
110
110
|
|
|
111
|
-
-
|
|
111
|
+
- Use SLIs for SLO paging; actionable capacity or impending-failure alerts may use internal measurements. See `references/alerting-and-on-call.md`.
|
|
112
112
|
- Severity levels: P0 (page on-call now), P1 (notify channel, ack within work hours), P2 (digest).
|
|
113
113
|
- Every alert MUST link to a runbook entry. If no runbook exists, the alert is not allowed to be P0.
|
|
114
114
|
- An alert-backed metric is a coverage contract, and the trigger is mechanical, not prose: any new or changed alert rule, SLO, dashboard alert annotation, or metric referenced by an alert policy fires this check (a written "alert on any increase" note also counts, but its absence is not an exemption). Enumerate every site that should feed the metric and verify each is actually instrumented — derive the site list from a static registry or lint where possible; the easiest site to miss is often the riskiest (e.g. the panic counter in a stream reader parsing untrusted bytes). Ship the site checklist with the alert.
|
|
@@ -208,7 +208,7 @@ If any phase has missing evidence, the work is not done.
|
|
|
208
208
|
- **"Sample everything vs head-sample 1%"** → head-sample low (1–10%) for cost; retaining errors/slow requests is **tail** sampling at the Collector and requires near-full SDK export to it (head-dropped spans never arrive) — pick one model per service, do not claim both. Sampling-by-route is acceptable for known noisy paths.
|
|
209
209
|
- **"Metrics egress: scrape vs push"** → each platform picks ONE canonical egress and every process uses it. A Prometheus-native platform may default to **scrape** (pull, with `up`/target-health + service discovery); an OTel-first platform, or workers/jobs/runtimes that can't be scraped, may default to **OTLP push** to a collector. Neither is universally better — don't overturn a working pull setup just to push. One egress per process for a given metric; a migration window may dual-write only with isolated pipelines / distinct metric names / a dedup plan. Long-term store stays separate from the short-term scrape/collect layer (R5).
|
|
210
210
|
- **"Add a new label to a metric"** → answer the cardinality question first. If max distinct values × series count > 1e6, refuse and use an exemplar trace instead.
|
|
211
|
-
- **"Alert on this symptom or that cause"** →
|
|
211
|
+
- **"Alert on this symptom or that cause"** → prefer user-visible symptoms; capacity or impending-failure warnings follow R7.
|
|
212
212
|
|
|
213
213
|
## Sanitization and Provenance
|
|
214
214
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Alerting and On-Call
|
|
2
2
|
|
|
3
|
+
## Choose an actionable signal
|
|
4
|
+
|
|
5
|
+
- **User symptoms and SLOs:** use service-level signals for user-symptom paging and the corresponding SLIs for SLO burn-rate alerts. Query the declared metric store; diagnostic counters do not become availability or latency SLIs merely because an alert uses them.
|
|
6
|
+
- **Capacity and impending failures:** white-box measurements may warn before user SLIs degrade, such as a credible forecast of disk exhaustion. Record the expected failure, the evidence for the threshold or forecast, the action, and the responsible owner. Choose urgency from impact and time left to act; a high internal metric alone is not a reason to page.
|
|
7
|
+
- **Diagnostic signals:** keep measurements with no actionable condition in dashboards or queries. Do not alert on every possible cause.
|
|
8
|
+
|
|
9
|
+
Verify new or changed alert rules against relevant failure, healthy, and recovery cases. Preserve the severity, ownership, runbook, and delivery requirements below for both symptom and impending-failure alerts. [Google SRE's Monitoring Distributed Systems](https://sre.google/sre-book/monitoring-distributed-systems/) describes both user symptoms and imminent saturation as alerting inputs.
|
|
10
|
+
|
|
3
11
|
## Two patterns
|
|
4
12
|
|
|
5
13
|
### Pattern A — Prometheus AlertManager
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md
CHANGED
|
@@ -100,10 +100,11 @@ Confusing these is the source of most "why doesn't this work" connectivity bugs.
|
|
|
100
100
|
|
|
101
101
|
### R4 — Default retry/timeout/circuit-breaker live at mesh, app overrides for business reasons
|
|
102
102
|
|
|
103
|
-
-
|
|
104
|
-
-
|
|
103
|
+
- On Istio HTTP/gRPC paths, `VirtualService` HTTP routes own request `timeout` and `retries`; `DestinationRule` owns connection-pool settings and `outlierDetection`. Other transports use their platform-owned equivalents.
|
|
104
|
+
- Before configuring retries or timers, read `references/retry-timeout-circuit-breaker.md` for field paths, single-owner retry selection, and idempotency checks. Both mesh and SDK follow them; a 5xx or missing response alone never proves replay safe.
|
|
105
|
+
- Framework client SDK may provide method-specific retry/hedging; its total call budget must fit the caller's remaining duration and any platform cap. A longer mesh timeout is a backstop, not an extended caller deadline.
|
|
105
106
|
- App code MAY override per-RPC. App code MUST NOT silently disable mesh-level outlier detection.
|
|
106
|
-
- Budget rule:
|
|
107
|
+
- Budget rule: downstream work, attempts, and backoff fit the remaining duration minus a safety margin (e.g. 100ms). Propagate deadline and cancellation; never reset the full budget at each hop.
|
|
107
108
|
|
|
108
109
|
### R5 — mTLS is mesh-default, app cannot disable
|
|
109
110
|
|
|
@@ -151,14 +152,13 @@ The client middleware fills this from ctx automatically; the server middleware e
|
|
|
151
152
|
|
|
152
153
|
For non-protobuf or metadata-only transports, an equivalent header set is compliant only when it cites a resolvable platform owner record, such as a repo/path, document URL, registry id, gateway policy, or owner-suite id. The owner record must enumerate the concrete header names. In diff-only review without resolver tooling, the diff must cite a stable owner-record locator and list the concrete header names inline; with resolver tooling, the reviewer or owner-suite may resolve the locator to those names instead. The enumerated header names must then be checked against the actual propagated and exposed headers: caller-supplied identity headers are absent unless positive authenticated-caller evidence exists. An unresolvable, non-enumerating, uncited, service-local, or unchecked header set is an open gap, not a permitted alternative. For pure HTTP (no RPC base), the equivalent is a stable owner-recorded header set, also filled by middleware.
|
|
153
154
|
|
|
154
|
-
### R8 — gRPC `:authority`
|
|
155
|
+
### R8 — gRPC `:authority` compatibility follows the actual request path
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
HTTP/2 `:authority` carries URI authority, not a single DNS label. An underscore alone does not prove a gRPC protocol violation. DNS hostnames, certificate identity checks, SDK validation, and proxy routing can impose different constraints; preserve the constraints of the deployed path.
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
2. **Mesh-level rewrite**: an EnvoyFilter Lua snippet replaces `_` with `-` in `:authority` for gRPC requests, before routing.
|
|
159
|
+
Before renaming a service or adding a rewrite, capture the failing request's authority, the rejecting layer and version, and the relevant error or trace. A platform may require DNS-compatible service names and enforce that policy at registration and CI, but a registry name need not be the wire authority.
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
A proxy rewrite is an option only when the request reaches that filter before the rejecting layer. It cannot fix a client rejection before transmission or an inbound parser rejection before the filter runs. Scope any verified rewrite to the affected traffic and explicit authority mappings; retain route, TLS identity, and authorization checks. Diagnosis, migration, and verification: `references/grpc-authority-workaround.md`.
|
|
162
162
|
|
|
163
163
|
### R9 — Ingress and egress are explicit, not implicit
|
|
164
164
|
|
|
@@ -200,8 +200,8 @@ Option 1 is cleaner long-term; option 2 is the live-system workaround. Document
|
|
|
200
200
|
### Phase B — Mesh policy
|
|
201
201
|
|
|
202
202
|
1. PeerAuthentication = STRICT (mTLS namespace-wide).
|
|
203
|
-
2. DestinationRule per critical callee:
|
|
204
|
-
3. VirtualService
|
|
203
|
+
2. DestinationRule per critical callee: connection-pool limits, connect timeout, outlier detection.
|
|
204
|
+
3. VirtualService HTTP routes: lane header match → lane subset, request timeout, and the R4 retry policy.
|
|
205
205
|
4. AuthorizationPolicy expresses which services may call which (zero-trust at network level).
|
|
206
206
|
|
|
207
207
|
### Phase C — Service discovery
|
|
@@ -216,7 +216,7 @@ Option 1 is cleaner long-term; option 2 is the live-system workaround. Document
|
|
|
216
216
|
|
|
217
217
|
1. Read the framework default client/server options module. Confirm middleware chain matches R6.
|
|
218
218
|
2. Confirm dev cannot build a client/server without inheriting these.
|
|
219
|
-
3. Test:
|
|
219
|
+
3. Test: induce the configured outlier threshold on one callee → verify ejection and recovery, the configured retry layer retries only eligible calls within its budget, and exhausted calls propagate a stable error-code.
|
|
220
220
|
|
|
221
221
|
### Phase E — Failure modes
|
|
222
222
|
|
|
@@ -234,7 +234,7 @@ Before marking work done:
|
|
|
234
234
|
|
|
235
235
|
## Decision Points
|
|
236
236
|
|
|
237
|
-
- **"Add a new retry policy for service X"** →
|
|
237
|
+
- **"Add a new retry policy for service X"** → apply R4's replay-safety and single-owner checks. Use the matching VirtualService HTTP route for Istio retries; disable and verify route retries when the SDK owns them. DestinationRule limits concurrent retries, not per-request attempts.
|
|
238
238
|
- **"Service A times out calling Service B"** → check three layers in order: app deadline (ctx timeout) → framework client timeout → mesh request timeout. Whichever is smaller wins; align them.
|
|
239
239
|
- **"Switch service discovery mode"** → use `references/service-discovery-choice.md`; do not mandate registry unless the platform needs registry-specific capabilities such as per-instance drain, out-of-cluster lookup, or existing registry federation.
|
|
240
240
|
- **"Need mTLS to a non-mesh external service"** → egress gateway with terminating TLS, not app-managed certs.
|
|
@@ -257,7 +257,7 @@ Reused industry patterns (PSM-style identity, `<owner>.<class>.<env>` shape, `tr
|
|
|
257
257
|
- `references/framework-middleware.md` — Server and client middleware chain (HTTP + RPC); the canonical RPC base struct shape; verification commands.
|
|
258
258
|
- `references/multi-env-routing.md` — Lane label end-to-end recipe; VirtualService patterns; queue-boundary propagation; stress/shadow tags.
|
|
259
259
|
- `references/retry-timeout-circuit-breaker.md` — Mesh defaults vs SDK overrides; cascading timeout budgets; idempotency awareness; outlier detection tuning.
|
|
260
|
-
- `references/grpc-authority-workaround.md` —
|
|
260
|
+
- `references/grpc-authority-workaround.md` — Locate authority rejection; distinguish naming policy from protocol syntax; verify scoped compatibility changes.
|
|
261
261
|
- `references/dual-sidecar-and-traffic-config-center.md` — Pod-level dual sidecar (mesh + platform), per-protocol mesh injection policy, per-caller-callee traffic config via config center (separate from mesh routing).
|
|
262
262
|
- `references/rpc-framework-recipe.md` — Concrete kitex/hertz default suite: shared RPC base field contract, RPC base.Request full schema (8 fields incl From/To), dual-channel ctx propagation (metainfo + grpc metadata), 9-code error enum + framework error mapping table, three resolver strategies (registry / FQDN fallback / proxy), platform latency histogram buckets, CORS defaults exposing log-id header, server boot sequence with graceful shutdown.
|
|
263
263
|
- `references/service-discovery-choice.md` — Decision framework: registry-based vs k8s-native SD; both support lane routing + canary + multi-env; pick by per-instance drain need, laptop access pattern, federation preference, operational burden; mixed mode (k8s east-west + thin registry for laptop) is workable; migration paths in both directions.
|
|
@@ -269,7 +269,7 @@ Reused industry patterns (PSM-style identity, `<owner>.<class>.<env>` shape, `tr
|
|
|
269
269
|
|
|
270
270
|
1. **Static**: framework default options module includes all R6 middleware; mesh PeerAuthentication is STRICT; DestinationRule and VirtualService exist for every callee that participates in lane routing.
|
|
271
271
|
2. **Live, identity**: cross-service trace shows log-id + lane consistent across all hops; mesh access log lines include both.
|
|
272
|
-
3. **Live, mesh policy**:
|
|
272
|
+
3. **Live, mesh policy**: trigger the configured outlier threshold with an eligible pool size; verify ejection and recovery against the effective policy and metrics. Consecutive-error checks are inline, not delayed until the periodic analysis interval.
|
|
273
273
|
4. **Live, lane routing**: send request with non-default lane → confirm only matching-lane instances serve it.
|
|
274
274
|
5. **Static, no leakage**: grep this skill's content — zero internal hostnames, repo names, or business terms.
|
|
275
275
|
|
|
@@ -36,7 +36,7 @@ Mesh injection is not all-or-nothing. Real platforms apply per-protocol policy:
|
|
|
36
36
|
| App protocol | Inject Istio sidecar? | Reason |
|
|
37
37
|
|---|---|---|
|
|
38
38
|
| HTTP (REST) | YES | Envoy handles HTTP/1.1, HTTP/2; full feature support |
|
|
39
|
-
| gRPC | YES | HTTP/2 + per-call routing;
|
|
39
|
+
| gRPC | YES | HTTP/2 + per-call routing; verify `:authority` routing against the actual SDK/proxy path |
|
|
40
40
|
| TCP (raw) | NO (often) | Envoy TCP proxy is feature-poor; routing/auth less useful at L4 |
|
|
41
41
|
| Thrift (TTHeader) | NO (often) | Same — L4-ish; tooling limited |
|
|
42
42
|
|