Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/root-catalog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Root README catalog

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

jobs:
root-catalog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Check root README against the marketplace catalog
run: node --test --test-name-pattern='^root catalog:' test/production-links.test.mjs
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ copilot plugin install azure-functions-hosted-skills@azure-dev-tools
copilot plugin install azure-resources-query@azure-dev-tools
copilot plugin install azure-cost-health-check@azure-dev-tools
copilot plugin install azure-sre-agent@azure-dev-tools
copilot plugin install azure-storage-canvas@azure-dev-tools
copilot plugin install azure-storage@azure-dev-tools
copilot plugin install containers@azure-dev-tools
copilot plugin install canvas-authoring@azure-dev-tools
```
Expand All @@ -29,18 +29,18 @@ gear to add `microsoft/azure-dev-tools` (marketplace ID `azure-dev-tools`), and
select a plugin. Fully quit and reopen GitHub Copilot after installation, then
start a fresh chat.

The marketplace follows this repository's default branch. Immutable tags are
the reproducible install points:

| Plugin | What it does | Production status |
| --- | --- | --- |
| [Azure Functions Hosted Skills](canvases/azure-functions-hosted-skills/) | Build and run local Hosted Skills or invoke supported functions in an existing Function App. | 0.5.5 is tagged; `azure-functions-hosted-skills-latest` points to that release. |
| [Azure Resources Query](canvases/azure-resources-query/) | Find and inspect Azure resources with read-only Resource Graph queries. | 0.1.5 is tagged; `azure-resources-query-latest` points to that release. |
| [Canvas Authoring](plugins/canvas-authoring/) | Add toolkit setup and starter apps to the native `create-canvas` workflow. | 0.1.1 is tagged; `canvas-authoring-latest` points to that release. Skill-only plugin, with no canvas panel. |
| [Azure Cost Health Check](canvases/azure-cost-health-check/) | Review spend, forecasts, budgets, alerts, recommendations, and AI billing. | 0.4.5 is tagged; `azure-cost-health-check-latest` points to that release. |
| [Azure SRE Agent](canvases/azure-sre-agent/) | Connect to your SRE Agent, diagnose failing apps, and investigate incident threads. | 0.2.7 is tagged; `azure-sre-agent-latest` points to that release. |
| [Azure Storage canvas](canvases/azure-storage-canvas/) | Browse blobs, inspect statistics, download files, and plan storage transfers with Copilot. | 0.1.1; immutable and `azure-storage-canvas-latest` install links become usable after the reviewed merge and tag verification. |
| [Containers](canvases/containers/) | Browse and operate local Docker or Podman containers and images. | 0.1.1 candidate; immutable and `containers-latest` install links become usable after the reviewed merge and tag verification. |
The marketplace follows this repository's default branch. Choose a plugin by
what you want to do:

| Plugin | What it does |
| --- | --- |
| [Azure Functions Hosted Skills](canvases/azure-functions-hosted-skills/) | Build and run local Hosted Skills or invoke supported functions in an existing Function App. |
| [Azure Resources Query](canvases/azure-resources-query/) | Find and inspect Azure resources with read-only Resource Graph queries. |
| [Canvas Authoring](plugins/canvas-authoring/) | Add toolkit setup and starter apps to the native `create-canvas` workflow. Skill-only plugin, with no canvas panel. |
| [Azure Cost Health Check](canvases/azure-cost-health-check/) | Review spend, forecasts, budgets, alerts, recommendations, and AI billing. |
| [Azure SRE Agent](canvases/azure-sre-agent/) | Connect to your SRE Agent, diagnose failing apps, and investigate incident threads. |
| [Azure Storage](canvases/azure-storage/) | Browse blobs, inspect statistics, download files, and plan storage transfers with Copilot. |
| [Containers](canvases/containers/) | Browse and operate local Docker or Podman containers and images. |

Native App marketplace installation has not been verified for every product;
a source package or direct CLI install alone does not establish that the
Expand Down
20 changes: 19 additions & 1 deletion docs/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ checks for each new package tree.
Release order:

1. Start with content cleared for public distribution in this repository.
2. Run the package's required tests and release checks.
2. Run the package's required tests and release checks, including the root
catalog check described below.
3. Produce an inventory of the package files and record their SHA-256 digests
in `SHA256SUMS`.
4. Obtain explicit approval for the candidate and its customer-facing
Expand All @@ -100,6 +101,23 @@ Release order:
Never move `PRODUCT-latest` to an unmerged branch, candidate commit, or
unapproved rebuild.

### Keeping the root catalog current

Update authoritative marketplace metadata and product release documentation for
each release. Keep versions, immutable tags, and checksums in those release
records and product installation instructions, not in the root README.
Update the root README only when a product is added, renamed, or removed, or
its capabilities, install name, or product README path change.

Run `node --test --test-name-pattern='^root catalog:' test/production-links.test.mjs`.
The `Root README catalog` workflow in `.github/workflows/root-catalog.yml` runs
this guard on every pull request to `main` and every push to `main`, including
product release changes. It checks current catalog identities, install commands,
product README paths, and the skill-only and native marketplace qualifications.
Version-only updates need no root edit. This guard is separate from package
integrity, tag verification, and required release approvals; it replaces none
of them.

For the 0.5.3 Hosted Skills and 0.1.3 Azure Resources Query candidates, the
Agent Plugins v1 manifests, protected previews, namespaced extensions, and
complete new in-package `SHA256SUMS` receipts are verified with
Expand Down
109 changes: 87 additions & 22 deletions test/production-links.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,94 @@ test("customer installation docs identify the production repository, not public
}
});

test("production catalog matches all installable plugins in order", () => {
const readme = readFileSync(new URL("README.md", root), "utf8");
const rows = readme.split("\n").filter((line) => line.startsWith("| ["));
const products = {
"azure-functions-hosted-skills": ["Azure Functions Hosted Skills", "canvases/azure-functions-hosted-skills/"],
"azure-resources-query": ["Azure Resources Query", "canvases/azure-resources-query/"],
"canvas-authoring": ["Canvas Authoring", "plugins/canvas-authoring/"],
"azure-cost-health-check": ["Azure Cost Health Check", "canvases/azure-cost-health-check/"],
"azure-sre-agent": ["Azure SRE Agent", "canvases/azure-sre-agent/"],
"containers": ["Containers", "canvases/containers/"],
};
assert.equal(rows.length, manifest.plugins.length);
for (const [index, { name, version }] of manifest.plugins.entries()) {
const [label, path] = products[name];
assert.ok(rows[index].startsWith(`| [${label}](${path}) |`), `catalog order: ${label}`);
assert.ok(existsSync(new URL(path, root)), `${label}: missing package`);
assert.ok(rows[index].includes(version), `${label}: current catalog version`);
function verifyRootCatalog(readme, catalog) {
const rows = readme.split("\n").filter((line) => line.startsWith("|"));
assert.equal(rows.shift(), "| Plugin | What it does |");
assert.equal(rows.shift(), "| --- | --- |");
const paths = rows.map((row) => {
const match = /^\| \[([^\]]+)\]\(([^)]+)\) \| ([^|]+) \|$/.exec(row);
assert.ok(match, `root product row: ${row}`);
return match[2];
});
assert.equal(new Set(catalog.plugins.map(({ name }) => name)).size,
catalog.plugins.length, "unique catalog identities");
assert.deepEqual(paths, catalog.plugins.map(({ source }) => `${source}/`),
"root product paths match the current catalog in order");
const installs = [...readme.matchAll(/copilot plugin install (\S+)/g)]
.map((match) => match[1]).sort();
assert.deepEqual(installs, catalog.plugins.map(({ name }) => `${name}@${catalog.name}`).sort(),
"root install commands match the current catalog exactly");
for (const { source } of catalog.plugins) {
assert.ok(existsSync(new URL(`${source}/README.md`, root)),
`${source}: missing product README`);
}
const authoring = catalog.plugins.find(({ name }) => name === "canvas-authoring");
assert.ok(authoring, "skill-only companion remains in the catalog");
assert.match(rows[paths.indexOf(`${authoring.source}/`)],
/Skill-only plugin, with no canvas panel/);
assert.match(readme, /Native App marketplace installation has not been verified for every product/);
assert.match(readme, /a source package or direct CLI install alone does not establish that the\s+native listing is live/);
assert.match(readme, /Each product README lists its prerequisites, safe prompts, first-run workflow,\s+version-pinned installation options, and product-specific safety behavior/);
assert.doesNotMatch(readme, /\b\d+\.\d+\.\d+\b|-latest\b|-v\d|\b[0-9a-f]{7,64}\b/,
"release versions, tag aliases, and hashes belong in product release docs");
}

const rootReadme = readFileSync(new URL("README.md", root), "utf8");

test("root catalog: matches all current install names and product README paths", () => {
verifyRootCatalog(rootReadme, manifest);
});

test("root catalog: version-only updates need no root README edit", () => {
const bumped = structuredClone(manifest);
for (const plugin of bumped.plugins) plugin.version = "99.99.99";
assert.doesNotThrow(() => verifyRootCatalog(rootReadme, bumped));
});

test("root catalog: rejects added, removed, renamed, duplicate, or relocated catalog identities", () => {
for (const update of [
(catalog) => catalog.plugins.push({ name: "new-plugin", source: "canvases/new-plugin" }),
(catalog) => catalog.plugins.pop(),
(catalog) => { catalog.plugins[0].name = "renamed-plugin"; },
(catalog) => catalog.plugins.push(structuredClone(catalog.plugins[0])),
(catalog) => { catalog.plugins[0].source = "canvases/relocated-plugin"; },
]) {
const changed = structuredClone(manifest);
update(changed);
assert.throws(() => verifyRootCatalog(rootReadme, changed),
/root product paths|root install commands|unique catalog identities/);
}
});

test("root catalog: rejects missing, obsolete, extra, or incorrect README installs and paths", () => {
const row = rootReadme.split("\n").find((line) =>
line.startsWith("| [Azure Storage]"));
for (const changed of [
rootReadme.replace("copilot plugin install azure-storage@azure-dev-tools\n", ""),
rootReadme.replace("azure-storage@azure-dev-tools", "azure-storage-canvas@azure-dev-tools"),
rootReadme.replace("azure-storage@azure-dev-tools", "azure-storage@wrong-marketplace"),
`${rootReadme}\ncopilot plugin install extra-plugin@azure-dev-tools\n`,
rootReadme.replace(row, `${row}\n${row}`),
rootReadme.replace(`${row}\n`, ""),
rootReadme.replace("](canvases/azure-storage/)", "](canvases/azure-storage-canvas/)"),
]) {
assert.throws(() => verifyRootCatalog(changed, manifest),
/root product paths|root install commands/);
}
});

test("root catalog: retains skill-only, pinned-install, and native marketplace qualifications", () => {
for (const changed of [
rootReadme.replace("Skill-only plugin, with no canvas panel.", ""),
rootReadme.replace("Native App marketplace installation has not been verified for every product;", ""),
rootReadme.replace("version-pinned installation options", "installation options"),
`${rootReadme}\n0.4.1 is tagged; azure-sre-agent-latest points to that release.\n`,
`${rootReadme}\nSource revision: abcdef0\n`,
`${rootReadme}\nRelease commit: bc8c3349df123c56eea4a957f8d63d3a4a8fd6e4\n`,
`${rootReadme}\nSHA-256 checksum: ${"a".repeat(64)}\n`,
]) {
assert.throws(() => verifyRootCatalog(changed, manifest), assert.AssertionError);
}
assert.match(rows[2], /Skill-only plugin, with no canvas panel/);
assert.match(readme, /copilot plugin install azure-sre-agent@azure-dev-tools/);
assert.match(readme, /\[Azure SRE Agent\]\(canvases\/azure-sre-agent\/\)/);
assert.match(readme, /copilot plugin install containers@azure-dev-tools/);
assert.match(readme, /\[Containers\]\(canvases\/containers\/\)/);
});

test("SRE package has its catalog identity, official preview logo and customer connection steps", () => {
Expand Down
Loading