Skip to content

Commit 1a9849a

Browse files
📖 [Docs]: Repository Type Property standard + memory-repo ruleset fix (#76)
Adds [Repository Type Property](../blob/main/src/docs/Ways-of-Working/Repository-Type-Property.md) documenting the Type custom-property pattern used to classify repositories and drive which org-wide rulesets/controls apply to them. ## Why Standing up `MSXOrg/memory` per the [Memory Repository Template](../blob/main/src/docs/Frameworks/Agentic-Development/memory-template.md) surfaced a real gap: the org-wide "GitHub Flow" ruleset requires PRs on every push to main, which conflicts with the memory repo's designed no-PR, direct-commit workflow. Tracked in #71. This PR documents and implements the fix in both current MSX initiative organizations: - **MSXOrg**: introduced a new Type custom property (Docs, Memory, VSCodeExtension, Other), assigned it to all 7 repos, migrated the "GitHub Flow" ruleset from the old single-purpose BranchStrategy property to a Type-based exclude condition, verified 1:1 coverage parity across every repo, then retired BranchStrategy entirely. - **PSModule**: added Memory to the existing Type property's allowed values, set PSModule/memory's Type to Memory, migrated the ruleset from a name-based ~ALL condition to the same Type-based exclude pattern, and verified all 95 repos kept identical coverage except the intentionally-excluded memory repo (95/95 → 94/95). Both migrations used an **exclude-only** condition (Type != Memory is covered, not an include allow-list) specifically so future Type values are covered by default and nothing falls out of branch protection silently — the verification playbook for this is written into the new page. Also documents a real API quirk hit during implementation: PATCH on orgs/{org}/rulesets/{id} returned 404 for an otherwise fully-authorized token, including on a disposable ruleset created solely to isolate the bug. Ruleset edits were done via delete-old + create-new instead. Related: #71 (this PR resolves it for both organizations) 📖 [Docs] Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 7d0ad35 commit 1a9849a

6 files changed

Lines changed: 128 additions & 1 deletion

File tree

‎src/docs/Frameworks/Agentic-Development/memory-template.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ exception, made explicit rather than left as an oversight:
6565
`pull_request_template.md` and `CODEOWNERS` review routing make no sense for a repo
6666
where every change lands as a direct commit to `main`.
6767
See [Memory writing rules](design.md#memory-writing-rules) for what a good direct
68-
commit looks like.
68+
commit looks like. This only works in practice once the organization's ruleset stops
69+
requiring pull requests for `memory` repositories — see
70+
[Repository Type Property](../../Ways-of-Working/Repository-Type-Property.md) for how
71+
that exclusion is implemented via a `Type: Memory` custom property value.
6972
- **No external dependencies.** Plain Markdown files have no supply chain, so
7073
`.github/dependabot.yml` has nothing to update.
7174
- **Small, defined audience.** `SUPPORT.md` and `CODE_OF_CONDUCT.md` exist to set
@@ -90,3 +93,6 @@ public audience, even when the adjoining `docs` repository is public.
9093
set this page's scaffold deliberately departs from.
9194
- [Organization Standard](../../Ways-of-Working/Organization-Standard.md) — how
9295
initiative-defined, type-specific exceptions to the default file set are allowed.
96+
- [Repository Type Property](../../Ways-of-Working/Repository-Type-Property.md) — how a
97+
`Type: Memory` custom property value excludes memory repositories from the org-wide
98+
pull-request-required ruleset so the direct-commit workflow above actually works.

‎src/docs/Ways-of-Working/Organization-Standard.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Agent files are allowed when they point to, summarize, or operationalize the cen
9494
## Where this connects
9595

9696
- [Repository Standard](Repository-Standard.md) — the repository-level contract every repository must satisfy.
97+
- [Repository Type Property](Repository-Type-Property.md) — the concrete `Type` custom-property mechanism that implements "repository types" and "required custom properties, branch protection" from this page.
9798
- [Documentation Model](Documentation-Model.md) — why specs own why and what, while designs own implementation.
9899
- [Dependency Updates](../Capabilities/dependency-updates/spec.md) — the supply-chain update capability every repository inherits.
99100
- [GitHub Actions](../Coding-Standards/GitHub-Actions.md) — workflow authoring and enforcement rules.

‎src/docs/Ways-of-Working/Repository-Standard.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ For example, PSModule can define its module-specific managed files in `PSModule/
146146
## Where this connects
147147

148148
- [Organization Standard](Organization-Standard.md) — what an initiative organization must define centrally.
149+
- [Repository Type Property](Repository-Type-Property.md) — the `Type` custom property that classifies a repository and drives which type-specific files and controls apply.
149150
- [README-Driven Context](Readme-Driven-Context.md) — why the README is the front door.
150151
- [PR Format](PR-Format.md) — the PR Manager-style title and description format.
151152
- [GitHub Actions](../Coding-Standards/GitHub-Actions.md) — workflow and automation standards.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: Repository Type Property
3+
description: How a single "Type" custom property classifies every repository in an initiative organization and drives which org-wide controls apply to it.
4+
---
5+
6+
# Repository Type Property
7+
8+
[Organization Standard](Organization-Standard.md) requires every initiative to define
9+
"repository types used by the initiative" and "required custom properties, labels, branch
10+
protection, and review rules." This page is the concrete mechanism that satisfies both at
11+
once: a single GitHub organization **custom property named `Type`**, whose value per
12+
repository determines which org-wide rulesets and controls apply.
13+
14+
## The pattern
15+
16+
Each initiative organization defines:
17+
18+
1. One `single_select` custom property named `Type`, required on every repository, with a
19+
default value (typically `Other`).
20+
2. An allowed-values list specific to that organization's actual repository shapes (a docs
21+
org and a module-publishing org will not need the same list).
22+
3. Org-wide rulesets (branch protection, required reviews, and similar controls) that
23+
target repositories by their `Type` value instead of by repository name.
24+
25+
Setting a repository's `Type` is then the single action that determines every `Type`-scoped
26+
control it inherits — no per-repository ruleset edits, no repository-name lists to keep in
27+
sync by hand.
28+
29+
## Filter by exclusion, not by inclusion
30+
31+
When a ruleset condition targets `Type`, write it as an **exclude** list of the `Type`
32+
values that should be exempt, with an empty `include` list — not an include allow-list of
33+
the values that should be covered.
34+
35+
```json
36+
"repository_property": {
37+
"include": [],
38+
"exclude": [{ "name": "Type", "source": "custom", "property_values": ["Memory"] }]
39+
}
40+
```
41+
42+
An empty `include` array matches every repository; the `exclude` array then subtracts
43+
specific `Type` values. This means:
44+
45+
- Adding a brand-new `Type` value in the future (a new repository shape nobody has
46+
invented yet) is covered by the ruleset automatically, with no ruleset edit required.
47+
- Only `Type` values an administrator has explicitly named in `exclude` ever lose
48+
coverage. Nothing falls out silently.
49+
50+
An include allow-list inverts this safety property: any repository whose `Type` is not on
51+
the list silently loses coverage, including every future `Type` value nobody remembered to
52+
add. Exclude-based conditions are the only version of this pattern that is safe to extend
53+
over time.
54+
55+
## Verifying a migration before cutting over
56+
57+
Moving a ruleset from a name-based or single-purpose-property condition onto `Type` is a
58+
live change to branch protection. Verify it does not silently drop coverage for any
59+
repository before making it live:
60+
61+
1. Enumerate every repository in the organization and the `Type` (or prior property) value
62+
it currently has, via `GET /orgs/{org}/properties/values`.
63+
2. Compute, for the **current** ruleset condition, which repositories are covered.
64+
3. Create the replacement ruleset under a temporary name so both rulesets can exist side by
65+
side, then use `GET /repos/{owner}/{repo}/rules/branches/{branch}` per repository to
66+
compute which repositories the **new** condition actually covers (this is more reliable
67+
than reasoning about condition JSON by hand, since it reflects GitHub's own evaluation).
68+
4. Diff the two coverage sets. The only differences should be the `Type` values the
69+
migration intentionally excludes. Any other difference means the new condition is wrong.
70+
5. Only after the diff matches expectations: delete the old ruleset, then create the final
71+
ruleset under its real name, then delete the temporary one. This ordering means both
72+
rulesets are briefly active together rather than there being a gap with neither active.
73+
74+
## A known API quirk: ruleset `PATCH` may not work
75+
76+
`PATCH` on `/orgs/{org}/rulesets/{id}` has been observed to fail (`404`) for tokens that
77+
otherwise have full `GET`/`POST`/`DELETE` access to the same endpoint, including on a
78+
disposable test ruleset created solely to isolate the failure. Treat in-place ruleset edits
79+
as unreliable and use the delete-old / create-new sequence above instead, even for small
80+
condition changes.
81+
82+
## Deprecating single-purpose properties
83+
84+
An initiative may have started with a narrow, single-purpose property (for example, a
85+
`BranchStrategy` property with values like `None` / `GitHub Flow`, used only to gate one
86+
ruleset). Once a `Type` property exists and a ruleset condition has been migrated onto it
87+
and verified, retire the narrow property (`DELETE /orgs/{org}/properties/schema/{name}`)
88+
rather than keeping two overlapping classification properties. `Type` is the one property
89+
that should answer "what kind of repository is this," and every `Type`-scoped control
90+
should read from it.
91+
92+
## Worked examples
93+
94+
Both current MSX initiative organizations use this pattern:
95+
96+
| Organization | `Type` allowed values | Notes |
97+
| --- | --- | --- |
98+
| `MSXOrg` | `Docs`, `Memory`, `VSCodeExtension`, `Other` | Introduced from scratch, replacing a prior single-purpose `BranchStrategy` property. |
99+
| `PSModule` | `Action`, `Archive`, `Docs`, `Framework`, `FunctionApp`, `Memory`, `Module`, `Other`, `Template`, `Workflow` | `Memory` added to an existing, already-populated `Type` property; the ruleset condition changed from a repository-name allow-list (`~ALL`) to a `Type`-based exclude. |
100+
101+
In both organizations, repositories with `Type: Memory` — the
102+
[Memory Repository Template](../Frameworks/Agentic-Development/memory-template.md)'s
103+
no-PR, direct-commit-to-`main` repositories — are excluded from the org-wide pull-request-
104+
required ruleset. That template's workflow only works because the ruleset stops matching
105+
`Memory`-typed repositories; without this, direct pushes to a memory repository's `main`
106+
are rejected the same as on any other repository.
107+
108+
## Where this connects
109+
110+
- [Organization Standard](Organization-Standard.md) — the requirement this property
111+
implements: documented repository types and the custom properties, rulesets, and review
112+
rules attached to them.
113+
- [Repository Standard](Repository-Standard.md) — the mandatory/type-specific/
114+
repository-specific file-set distinction that `Type` also drives over time.
115+
- [Memory Repository Template](../Frameworks/Agentic-Development/memory-template.md) — the
116+
concrete repository type whose no-PR workflow motivated excluding `Type: Memory` from the
117+
pull-request-required ruleset in both current organizations.

‎src/docs/Ways-of-Working/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This section documents the principles, processes, and norms that every contribut
2525
| [Git Worktrees](Git-Worktrees.md) | How agentic development is implemented locally — a bare-clone and worktree layout for working on several things at once. |
2626
| [Organization Standard](Organization-Standard.md) | What every initiative organization must define centrally so humans and agents share the same expectations. |
2727
| [Repository Standard](Repository-Standard.md) | The baseline files and behaviours every repository must expose so it is understandable, secure, and maintainable. |
28+
| [Repository Type Property](Repository-Type-Property.md) | How a single "Type" custom property classifies every repository in an initiative organization and drives which org-wide controls apply to it. |
2829
| [Principles](Principles/index.md) | The foundational beliefs and product mindset behind every decision. |
2930
| [Engineering Taste](Engineering-Taste.md) | The judgment that takes over when the standards run out. |
3031
| [Goal-Setting Framework](Goal-Setting.md) | Mission, OKRs, and initiatives — strategy connected to delivery. |

‎src/zensical.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ nav = [
3737
]},
3838
{"Organization Standard" = "Ways-of-Working/Organization-Standard.md"},
3939
{"Repository Standard" = "Ways-of-Working/Repository-Standard.md"},
40+
{"Repository Type Property" = "Ways-of-Working/Repository-Type-Property.md"},
4041
{"Principles" = [
4142
"Ways-of-Working/Principles/index.md",
4243
{"Purpose and Direction" = "Ways-of-Working/Principles/Purpose-and-Direction.md"},

0 commit comments

Comments
 (0)