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
11 changes: 11 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Ownership for CI, release, and security-sensitive infrastructure.
# A PR that edits these paths requires review from a code owner once
# branch protection's "Require review from Code Owners" is enabled.

/.github/ @beardthelion
/Dockerfile @beardthelion
/Dockerfile.bins @beardthelion
/docker-compose.yml @beardthelion
/release-please-config.json @beardthelion
/.release-please-manifest.json @beardthelion
/SECURITY.md @beardthelion
66 changes: 66 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Bug report
description: Report a defect in a Gitlawb crate or node behavior
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for the report. Please fill in the fields below so we can reproduce the issue.
For security vulnerabilities, do NOT open an issue — follow SECURITY.md instead.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: A clear description of the bug.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: dropdown
id: crate
attributes:
label: Affected crate
options:
- gitlawb-node
- gl
- git-remote-gitlawb
- gitlawb-core
- not sure / other
validations:
required: true
- type: input
id: version
attributes:
label: Version or commit
description: Output of the binary's `--version`, or the git commit you built.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: OS, Rust version (`rustc --version`), and anything else relevant.
validations:
required: false
- type: textarea
id: logs
attributes:
label: Relevant logs
description: Paste logs or errors. This will be rendered as code, no need for backticks.
render: shell
validations:
required: false
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/Gitlawb/node/blob/main/SECURITY.md
about: Please report security issues privately per our security policy, not as a public issue.
- name: Questions & discussion
url: https://github.com/Gitlawb/node/discussions
about: For usage questions and open-ended discussion.
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Feature request
description: Suggest an improvement or new capability
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: Problem / use case
description: What are you trying to do that's hard or impossible today?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
validations:
required: false
- type: dropdown
id: protocol
attributes:
label: Does this touch the protocol?
description: Identity, signatures, UCAN, ref certs, or P2P wire formats. Protocol changes need an issue discussion before any PR.
options:
- "No"
- "Yes"
- "Not sure"
validations:
required: true
63 changes: 63 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!--
Thanks for contributing to Gitlawb. Keep PRs focused on one change.
Protocol-level changes (identity, signatures, wire format) should start
as an issue before code — see CONTRIBUTING.md.
-->

## Summary

<!-- One or two sentences: what changes and why it matters. -->



## Motivation & context

<!-- What problem does this solve? Link prior discussion if any. -->
Closes #

## Kind of change

- [ ] Bug fix
- [ ] Feature
- [ ] Security fix
- [ ] Docs
- [ ] Tests / CI
- [ ] Refactor (no behavior change)
- [ ] Breaking or protocol change (issue required first)

## What changed

<!-- Bullet the concrete changes. Note the crate(s) touched:
gitlawb-core / gitlawb-node / gl / git-remote-gitlawb -->

-

## How a reviewer can verify

<!-- Commands to run, or repro steps for a bug plus proof it's fixed. -->

```sh

```

## Before you request review

- [ ] Scope is one logical change; no unrelated churn
- [ ] `cargo test --workspace` passes locally
- [ ] New behavior is covered by tests (required for fixes)
- [ ] `cargo fmt --all` and `cargo clippy --workspace --all-targets -- -D warnings` are clean
- [ ] Commit titles use Conventional Commits (`feat(...)`, `fix(...)`, `docs(...)`)
- [ ] Docs / `.env.example` updated if behavior or config changed (or N/A)
- [ ] Checked existing PRs so this isn't a duplicate

## Protocol & signing impact

<!-- Delete this block if your change doesn't touch the protocol. -->

- [ ] Touches DID / `did:key`, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formats
- [ ] Discussed in an issue before implementation
- [ ] Backward-compatible with existing nodes and previously signed history

## Notes for reviewers

<!-- Anything out of scope, follow-ups, or known limitations. Optional. -->
120 changes: 120 additions & 0 deletions .github/workflows/pr-triage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: PR Triage

on:
pull_request:
types: [opened, edited, synchronize, reopened]
Comment on lines +3 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For GitHub Actions as of 2026: when a workflow is triggered by pull_requestfrom a fork, canGITHUB_TOKENwithissues: write/pull-requests: write actually add labels and post PR comments, or is it still read-only?

💡 Result:

As of 2026, when a workflow is triggered by the pull_request event from a forked repository, the GITHUB_TOKEN is automatically restricted to read-only permissions, regardless of any permissions configuration defined in your workflow [1][2][3]. This is a security feature designed to prevent unauthorized code in a fork from accessing secrets or performing write operations in the target repository [4][5]. Consequently, attempts to add labels or post PR comments using this token will fail with a "Resource not accessible by integration" (403) error [1][6][7]. To perform these actions for pull requests from forks, you must use the pull_request_target event instead of pull_request [1][6][5]. The pull_request_target event triggers the workflow in the context of the base repository (the target branch) rather than the fork's branch, which allows the GITHUB_TOKEN to retain the write permissions granted in your workflow's permissions block [1][7][5]. When using pull_request_target, it is critical to observe security best practices, as the workflow has access to the target repository's secrets and write permissions [1][4]. Specifically: 1. Never check out or execute code directly from the forked PR branch if you use pull_request_target, as this could allow malicious code to access your repository's tokens and secrets [1][7]. 2. If you need to process PR data (like the title, body, or issue number), use the data available in the github.event context rather than checking out the untrusted code [7]. 3. Ensure your workflow only performs the intended automation (such as applying labels or posting a standard comment) and does not inadvertently expose the token to untrusted user input [7].

Citations:


🏁 Script executed:

cat -n .github/workflows/pr-triage.yml | sed -n '1,10p'

Repository: Gitlawb/node

Length of output: 284


🏁 Script executed:

wc -l .github/workflows/pr-triage.yml

Repository: Gitlawb/node

Length of output: 92


🏁 Script executed:

cat -n .github/workflows/pr-triage.yml | sed -n '3,5p'

Repository: Gitlawb/node

Length of output: 148


🏁 Script executed:

cat -n .github/workflows/pr-triage.yml | sed -n '80,130p'

Repository: Gitlawb/node

Length of output: 2522


Switch to pull_request_target event to enable fork PR write operations.

The current pull_request trigger automatically restricts GITHUB_TOKEN to read-only for forked PRs, causing the label and comment writes at lines 85, 88, and 120 to fail with a 403 error. Use pull_request_target instead, which runs in the base repository context and preserves write permissions. Note: when using pull_request_target, avoid checking out or executing untrusted code from the PR branch; use github.event context data instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-triage.yml around lines 3 - 5, The workflow currently
uses the `pull_request` event trigger which restricts GITHUB_TOKEN to read-only
permissions for forked PRs, causing write operations (labels and comments) to
fail with a 403 error. Change the trigger from `pull_request` to
`pull_request_target` in the `on:` section to run in the base repository context
with write permissions. When making this change, ensure that any steps writing
labels or comments do not check out or execute untrusted code from the PR
branch; instead, use the `github.event` context data directly to safely access
PR information while preserving write access.

Source: MCP tools


permissions:
contents: read
pull-requests: write
issues: write

concurrency:
group: pr-triage-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
triage:
name: Quality-signal triage
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Label and guide
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const pr = context.payload.pull_request;
const { owner, repo } = context.repo;
const prNumber = pr.number;

// Trusted authors bypass triage entirely.
const trusted = ["OWNER", "MEMBER", "COLLABORATOR"];
if (trusted.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is trusted; skipping triage.`);
return;
}

// Labels this workflow owns. Anything here may be added or removed each run.
const MANAGED = ["needs-issue", "needs-description", "needs-tests", "workflow-change"];
const want = new Set();

const body = pr.body || "";

// Linked issue: look for "closes/fixes/resolves #N" or any bare #N reference.
const hasLinkedIssue =
/\b(close[sd]?|fix(e[sd])?|resolve[sd]?)\b[^\n]*#\d+/i.test(body) ||
/#\d+/.test(body);
if (!hasLinkedIssue) want.add("needs-issue");

// Description substance: strip HTML comments and markdown headings, then measure.
const stripped = body
.replace(/<!--[\s\S]*?-->/g, "")
.replace(/^#+.*$/gm, "")
.trim();
if (stripped.length < 50) want.add("needs-description");

// File-based signals.
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: prNumber, per_page: 100,
});
const names = files.map(f => f.filename);

const isFork = pr.head.repo && pr.head.repo.full_name !== pr.base.repo.full_name;
const touchesWorkflows = names.some(n => n.startsWith(".github/workflows/"));
if (isFork && touchesWorkflows) want.add("workflow-change");

const changedRust = names.some(n => n.startsWith("crates/") && n.endsWith(".rs"));
// A test can live in a tests/ dir, a *_test.rs file, or — the common Rust
// pattern — an inline #[test] / #[cfg(test)] block added inside the source file.
const addsInlineTest = files.some(f =>
f.filename.endsWith(".rs") && f.patch &&
/^\+.*#\[(test\]|cfg\(test\))/m.test(f.patch));
const touchedTests =
names.some(n => n.includes("/tests/") || n.endsWith("_test.rs")) || addsInlineTest;
if (changedRust && !touchedTests) want.add("needs-tests");
Comment on lines +72 to +74

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Scope the test signal to Rust test paths only.

Line 72 currently treats any "/tests/" path as satisfying Rust test coverage. That can clear needs-tests even when Rust source changed but only non-Rust tests were touched.

Suggested fix
-            const touchedTests =
-              names.some(n => n.includes("/tests/") || n.endsWith("_test.rs")) || addsInlineTest;
-            if (changedRust && !touchedTests) want.add("needs-tests");
+            const touchedRustTests =
+              names.some(n =>
+                n.startsWith("crates/") &&
+                n.endsWith(".rs") &&
+                (n.includes("/tests/") || n.endsWith("_test.rs") || n.endsWith("_tests.rs"))
+              ) || addsInlineTest;
+            if (changedRust && !touchedRustTests) want.add("needs-tests");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-triage.yml around lines 72 - 74, The touchedTests
variable currently satisfies test coverage when any file path includes "/tests/"
regardless of file type, which incorrectly clears the needs-tests label when
non-Rust tests are touched but Rust source changed. Modify the condition in the
names.some() check to scope the "/tests/" path detection to Rust files only by
requiring files in "/tests/" directories to also end with the ".rs" extension,
ensuring only Rust test files satisfy the test coverage requirement.


// Reconcile labels: add wanted managed labels, remove managed labels no longer wanted.
const current = (await github.rest.issues.listLabelsOnIssue({
owner, repo, issue_number: prNumber,
})).data.map(l => l.name);

const toAdd = [...want].filter(l => !current.includes(l));
const toRemove = MANAGED.filter(l => current.includes(l) && !want.has(l));

if (toAdd.length) {
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: toAdd });
}
for (const name of toRemove) {
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name })
.catch(() => {}); // ignore if already gone
}

// Guidance comment: only for advisory signals, and only once.
const advisory = ["needs-issue", "needs-description", "needs-tests"].filter(l => want.has(l));
if (!advisory.length) return;

const MARKER = "<!-- gitlawb-triage -->";
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});
if (comments.some(c => c.body && c.body.includes(MARKER))) {
core.info("Guidance comment already present; not reposting.");
return;
}

const reasons = {
"needs-issue": "Link the issue this addresses (`Closes #123`). For protocol changes, open an issue first.",
"needs-description": "Add a short summary of what changes and why — the template's Summary and Motivation sections.",
"needs-tests": "This changes Rust source but no tests changed. Tests are required for fixes and strongly encouraged for features.",
};
const lines = advisory.map(l => `- ${reasons[l]}`).join("\n");
const comment = [
MARKER,
"Thanks for the contribution. A couple of things will help us review this faster:",
"",
lines,
"",
"See [CONTRIBUTING.md](https://github.com/Gitlawb/node/blob/main/CONTRIBUTING.md). Update the PR and these notes will clear automatically.",
].join("\n");

await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: comment });
39 changes: 39 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Stale PRs

on:
schedule:
- cron: "30 1 * * *" # daily at 01:30 UTC
workflow_dispatch:

permissions:
contents: read
pull-requests: write
issues: write

jobs:
stale:
name: Flag and close ignored PRs
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
days-before-stale: 14
days-before-close: 7
# Act on PRs only; never on issues.
days-before-issue-stale: -1
days-before-issue-close: -1
# Only PRs the triage step flagged as needing an author response.
only-pr-labels: "needs-issue,needs-description"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify actions/stale input semantics directly from upstream action metadata/docs.
set -euo pipefail
curl -fsSL https://raw.githubusercontent.com/actions/stale/main/README.md | rg -n "only-pr-labels|any-of-pr-labels|contain all|contain one"

Repository: Gitlawb/node

Length of output: 2219


Change only-pr-labels to any-of-pr-labels to match intended triage behavior.

The only-pr-labels filter requires PRs to have all specified labels. This means PRs with only needs-issue or only needs-description will not be marked stale, conflicting with the intended policy. Use any-of-pr-labels to process PRs that contain any one of these labels.

Suggested fix
-          only-pr-labels: "needs-issue,needs-description"
+          any-of-pr-labels: "needs-issue,needs-description"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
only-pr-labels: "needs-issue,needs-description"
any-of-pr-labels: "needs-issue,needs-description"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/stale.yml at line 27, The `only-pr-labels` configuration
requires PRs to have all specified labels simultaneously, but the intended
behavior is to mark PRs stale if they contain any of the specified labels.
Replace the `only-pr-labels` key with `any-of-pr-labels` in the stale workflow
configuration to ensure PRs that have either "needs-issue" or
"needs-description" labels are processed according to the triage policy.

Source: MCP tools

stale-pr-label: "stale"
exempt-pr-labels: "workflow-change,security,pinned"
remove-stale-when-updated: true
stale-pr-message: >
This PR has been inactive for 14 days and is still missing a linked issue
or a description. It will be closed in 7 days if there's no update. Push a
change or leave a comment to keep it open — no hard feelings, you can reopen
anytime.
close-pr-message: >
Closing due to inactivity. Reopen whenever you're ready to update it — see
CONTRIBUTING.md for what helps us review quickly.
ascending: true
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ Smart contracts live in a separate repo: [github.com/Gitlawb/contracts](https://
4. **Conventional commits.** Use `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`. Releases are automated by [release-please](https://github.com/googleapis/release-please) — your commit prefixes drive the next version bump.
5. **Format and lint.** Run `cargo fmt --all` and `cargo clippy --workspace --all-targets -- -D warnings` before submitting. CI will reject anything that fails these.

## What gets merged, what gets closed

We welcome contributions from humans and agents alike. To keep review sustainable, PRs are
expected to clear a basic quality bar:

- **Link an issue.** Bug fixes and features should reference an issue (`Closes #123`). For
protocol-level changes (identity, signatures, UCAN, ref certs, wire formats), open the
issue *before* writing code.
- **One change per PR.** Unrelated churn slows review and gets sent back.
- **Tests and a green pipeline.** New behavior needs tests; `cargo fmt`, `cargo clippy`,
and the full CI suite must pass.
- **A real description.** Say what changes and why. "Update code" is not a description.

A triage bot labels PRs that are missing these and leaves a short note. Nothing is closed
automatically while you're engaging. A flagged PR that goes 14 days with no linked issue or
description gets a stale warning, and is closed 7 days later if still untouched. Closed PRs can
be reopened at any time once updated.

## Development environment

**Requirements:**
Expand Down
Loading