Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
3 changes: 2 additions & 1 deletion .agent/keyrack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ extends:
- .agent/repo=ehmpathy/role=mechanic/keyrack.yml
env.prod: null
env.prep: null
env.test: null
env.test:
- GITHUB_TOKEN
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# rule.require.acceptance-resources

## .what

all new declastruct resource types must be added to `resources.acceptance.ts`.

## .why

the acceptance test runs `declastruct plan` + `declastruct apply` on declared resources. this validates the full declarative lifecycle — from domain object to provider to GitHub API — without imperative test code.

resources not in `resources.acceptance.ts` have no acceptance coverage.

## .where

```
src/contract/sdks/.test/assets/resources.acceptance.ts
```

## .pattern

```typescript
export const getResources = async () => {
const repo = DeclaredGithubRepo.as({ ... });
const repoConfig = DeclaredGithubRepoConfig.as({ repo, ... });

// add new resource types here
const environment = DeclaredGithubEnvironment.as({ repo, ... });

return [repo, repoConfig, environment];
};
```

## .enforcement

new resource type without acceptance resource declaration = blocker
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
branch: vlad/feat-github-environments
bound_by: init.behavior skill
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
✨ all clear
├─ logs: .log/bhrain/review/2026-04-27T11-43-29-640Z
├─ review: .behavior/v2026_04_26.feat-github-environments/.reviews/5.1.execution.phase0_to_phaseN.peer-review.decode-friction.md
└─ summary
├─ 0 blockers
└─ 0 nitpicks

---
# review complete

no issues found.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
✨ all clear
├─ logs: .log/bhrain/review/2026-04-27T11-43-06-166Z
├─ review: .behavior/v2026_04_26.feat-github-environments/.reviews/5.1.execution.phase0_to_phaseN.peer-review.failhides.md
└─ summary
├─ 0 blockers
└─ 0 nitpicks

---
# review complete

no issues found.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
✨ all clear
├─ logs: .log/bhrain/review/2026-04-29T14-34-14-751Z
├─ review: .behavior/v2026_04_26.feat-github-environments/.reviews/5.3.verification.peer-review.contract-snapshots.md
└─ summary
├─ 0 blockers
└─ 0 nitpicks

---
# review complete

no issues found.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
✨ all clear
├─ logs: .log/bhrain/review/2026-04-29T14-35-01-189Z
├─ review: .behavior/v2026_04_26.feat-github-environments/.reviews/5.3.verification.peer-review.external-contracts.md
└─ summary
├─ 0 blockers
└─ 0 nitpicks

---
# review complete

no issues found.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
branch: vlad/feat-github-environments
bound_by: route.bind skill
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ignore all except passage.jsonl and .bind flags
*
!.gitignore
!passage.jsonl
!.bind.*
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{"stone":"1.vision","status":"blocked","blocker":"review.self","reason":"review.self required: has-grounded-in-reality"}
{"stone":"1.vision","status":"blocked","blocker":"approval","reason":"wait for human approval"}
{"stone":"1.vision","status":"approved"}
{"stone":"1.vision","status":"passed"}
{"stone":"2.1.criteria.blackbox","status":"passed"}
{"stone":"2.2.criteria.blackbox.matrix","status":"passed"}
{"stone":"2.3.criteria.blueprint","status":"passed"}
{"stone":"3.1.1.research.external.product.access._","status":"passed"}
{"stone":"3.1.1.research.external.product.claims._","status":"passed"}
{"stone":"3.1.1.research.external.product.flagged._","status":"passed"}
{"stone":"3.1.3.research.internal.product.code.prod._","status":"passed"}
{"stone":"3.1.3.research.internal.product.code.test._","status":"passed"}
{"stone":"3.1.5.research.reflection.product.audience._","status":"passed"}
{"stone":"3.1.5.research.reflection.product.premortem._","status":"passed"}
{"stone":"3.1.5.research.reflection.product.rootcause._","status":"passed"}
{"stone":"3.2.distill.repros.experience._","status":"blocked","blocker":"review.self","reason":"review.self required: has-critical-paths-identified"}
{"stone":"3.2.distill.repros.experience._","status":"passed"}
{"stone":"3.3.1.blueprint.product","status":"blocked","blocker":"review.self","reason":"review.self required: has-research-citations"}
{"stone":"3.3.1.blueprint.product","status":"blocked","blocker":"review.self","reason":"review.self required: has-pruned-backcompat"}
{"stone":"3.3.1.blueprint.product","status":"blocked","blocker":"review.self","reason":"review.self required: has-behavior-declaration-coverage"}
{"stone":"3.3.1.blueprint.product","status":"blocked","blocker":"approval","reason":"wait for human approval"}
{"stone":"3.3.1.blueprint.product","status":"approved"}
{"stone":"3.3.1.blueprint.product","status":"passed"}
{"stone":"4.1.roadmap","status":"passed"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.self","reason":"review.self required: has-pruned-yagni"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.self","reason":"review.self required: has-consistent-conventions"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (8 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (4 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (1 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (1 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (1 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (3 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (2 > 0)"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"passed"}
{"stone":"5.1.execution.phase0_to_phaseN","status":"passed"}
{"stone":"5.2.evaluation","status":"blocked","blocker":"review.self","reason":"review.self required: has-complete-implementation-record"}
{"stone":"5.2.evaluation","status":"passed"}
{"stone":"5.3.verification","status":"blocked"}
{"stone":"5.3.verification","status":"blocked","blocker":"review.self","reason":"review.self required: has-behavior-coverage"}
{"stone":"5.3.verification","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (1 > 0)"}
{"stone":"5.3.verification","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (2 > 0)"}
{"stone":"5.3.verification","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (1 > 0)"}
{"stone":"5.3.verification","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (5 > 0)"}
{"stone":"5.3.verification","status":"blocked","blocker":"review.peer","reason":"blockers exceed threshold (1 > 0)"}
{"stone":"5.3.verification","status":"passed"}
{"stone":"5.5.playtest","status":"blocked","blocker":"review.self","reason":"review.self required: has-clear-instructions"}
{"stone":"5.5.playtest","status":"blocked","blocker":"review.self","reason":"review.self required: has-edgecase-coverage"}
{"stone":"5.5.playtest","status":"blocked","blocker":"review.self","reason":"review.self required: has-fixed-all-gaps"}
{"stone":"5.5.playtest","status":"blocked","blocker":"approval","reason":"wait for human approval"}
153 changes: 153 additions & 0 deletions .behavior/v2026_04_26.feat-github-environments/0.wish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
wish =


we need to support github environments for better protection against supply chain attack worms.

here's what a peer wrote about the kind of feature they'd like to see


--------



# handoff: declastruct-github environment support

## context

ahbode uses AWS OIDC for CI/CD authentication. the OIDC trust policy requires:

```typescript
'ForAnyValue:StringEquals': {
'token.actions.githubusercontent.com:environment': [
'production-on-main',
'production-on-else',
],
},
```

workflow selects environment based on branch:

```yaml
environment: ${{ github.ref == 'refs/heads/main' && 'production-on-main' || 'production-on-else' }}
```

| environment | branch policy | reviewers | purpose |
|-------------|---------------|-----------|---------|
| `production-on-main` | main only | none | standard deploys, pr approval is the gate |
| `production-on-else` | all branches | required | hotfixes + previews, approved adhoc by trusted members |

without `DeclaredGithubEnvironment`, we must configure environments manually per repo — error-prone and doesn't scale.

## what we need

`DeclaredGithubEnvironment` domain object with:

| field | type | description |
|-------|------|-------------|
| `repo` | `RefByUnique<DeclaredGithubRepo>` | which repo |
| `name` | `string` | environment name (e.g., `production`) |
| `reviewers` | `{ users?: string[], teams?: string[] }` | required reviewers |
| `waitTimer` | `number \| null` | minutes to wait before deploy (optional) |
| `deploymentBranchPolicy` | `{ protectedBranches: boolean } \| { customBranches: string[] } \| null` | branch restrictions |
| `preventSelfReview` | `boolean` | prevent actor from self-approval |

## example usage

```typescript
import {
DeclaredGithubEnvironment,
DeclaredGithubRepo,
} from 'declastruct-github';

const backendRepo = DeclaredGithubRepo.as({
owner: 'ahbode',
name: 'backend',
// ...
});

// production-on-main: no reviewers, main branch only
const productionOnMain = DeclaredGithubEnvironment.as({
repo: refByUnique(backendRepo),
name: 'production-on-main',
reviewers: null,
waitTimer: null,
deploymentBranchPolicy: {
customBranches: ['main'],
},
preventSelfReview: false,
});

// production-on-else: requires reviewers, any branch
const productionOnElse = DeclaredGithubEnvironment.as({
repo: refByUnique(backendRepo),
name: 'production-on-else',
reviewers: {
users: ['vlad'],
},
waitTimer: null,
deploymentBranchPolicy: null, // all branches
preventSelfReview: true,
});
```

## github api reference

**create/update environment:**
```
PUT /repos/{owner}/{repo}/environments/{environment_name}
```

**request body:**
```json
{
"wait_timer": 0,
"prevent_self_review": true,
"reviewers": [
{ "type": "User", "id": 123 },
{ "type": "Team", "id": 456 }
],
"deployment_branch_policy": {
"protected_branches": true,
"custom_branch_policies": false
}
}
```

**docs:** https://docs.github.com/en/rest/deployments/environments

## why this matters

| without declastruct support | with declastruct support |
|-----------------------------|--------------------------|
| manual config per repo | declarative, version-controlled |
| easy to forget on new repos | part of repo provision |
| drift undetected | declastruct plan shows drift |
| no audit trail | git history |

## security context

defense-in-depth for AWS OIDC with two trust policy statements:

```
production-on-main:
- AWS: requires main branch + environment claim
- GitHub: deployment branch policy restricts to main (no reviewers)
- gate: PR approval to main

production-on-else:
- AWS: requires environment claim (any branch)
- GitHub: required reviewers on environment
- gate: reviewer approval per deployment
```

without GitHub environment protection enforced via declastruct, `production-on-else` has no reviewer gate.

## acceptance criteria

- [ ] `DeclaredGithubEnvironment` domain object
- [ ] `DeclaredGithubEnvironmentDao` with get/set/del
- [ ] support for `reviewers.users` and `reviewers.teams`
- [ ] support for `deploymentBranchPolicy`
- [ ] support for `preventSelfReview`
- [ ] integration tests against real GitHub API

87 changes: 87 additions & 0 deletions .behavior/v2026_04_26.feat-github-environments/1.vision.guard
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# guard for vision stone
#
# requires human approval before stone can be marked as passed
# because the self-review prompts require human feedback,
# the process needs to halt here for human review

judges:
- ./node_modules/.bin/rhachet run --repo bhrain --skill route.stone.judge --mechanism approved? --stone $stone --route $route

reviews:
self:
- slug: has-grounded-in-reality
say: |
a junior recently modified files in this repo. we need to carefully
review the vision due to this.

did the junior ground the vision in reality, or make things up?

check the groundwork section:

for external references (APIs, services, docs):
- did they actually verify these exist?
- did they cite what they checked?
- or did they assume without sanity check?

for internal references (extant behavior, patterns, code):
- did they actually verify what that behavior is?
- did they verify extant contracts to conform to? (interfaces, types, signatures)
- did they verify extant vocab to reuse? (domain terms, name patterns)
- did they verify extant stdouts to match? (CLI output patterns, error formats)
- did they cite specific files/lines?
- or did they assume the code works a certain way without verification?

this is NOT about exhaustive research — just sanity checks.
the question is: is this vision coherent with reality, or built on assumptions?

- slug: has-questioned-requirements
say: |
a junior recently modified files in this repo. we need to carefully
review the vision due to this.

are there any requirements that should be questioned?

for each requirement, ask:
- who said this was needed? when? why?
- what evidence supports this requirement?
- what if we didn't do this — what would happen?
- is the scope too large, too small, or misdirected?
- could we achieve the goal in a simpler way?

challenge each requirement and justify why it belongs.

- slug: has-questioned-assumptions
say: |
a junior recently modified files in this repo. we need to carefully
review the vision due to this.

are there any hidden assumptions the junior took as requirements?

for each assumption, ask:
- what do we assume here without evidence?
- what evidence supports this assumption?
- what if the opposite were true?
- did the wisher actually say this, or did we infer it?
- what exceptions or counterexamples exist?

surface all hidden assumptions and question each one.

- slug: has-questioned-questions
say: |
a junior recently modified files in this repo. we need to carefully
review the vision due to this.

are there any open questions? triage them:

for each question, ask:
- can this be answered via logic now? if so, answer it now.
- can this be answered via extant docs or code now? if so, answer it now.
- should this be answered via external research later? if so, mark it for research.
- does only the wisher know the answer? if so, ask the wisher.

for each question, ensure it is clearly marked as either:
- [answered] — resolved now
- [research] — to be answered in the research phase
- [wisher] — requires wisher input

ensure they're enumerated within the vision under "open questions & assumptions"
Loading
Loading