@stream44.studio/dco 0.3.0-rc.2

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.
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "envelope": "ur:envelope/lrtpsotanshdhdcxdkchoebkgohkmyahwnmevtvtrhmytpjsztzemdfdtkvydsjecwpetyimmwfljpksoyaylftpsohdgltansgylftanshfhdcxdthsctfzbdihfmueaygdlpsfrtoeswaxlkeoaobyhpiofsindtfymylosaeofneotansgrhdcxchweztoevyckleryrdsslaiawdgtperyckfplbtkbbwytljotkfnesiycmfhspeeoycsfncsfgoycsfztpsohdjktngdgmgwhflfaxhdimyawzaxjtlddndnlecyqzvdclkskotksafsskdmlgwpbgdszmrerncmuofpaxmkkeahluwnmoidbsiypedmaozsneykjpplurloolghesjedwjtbzstimswwladghiartaxjptaeemewdesehahvybtfleowsjsvavwmtvarfsbwkhestknprpywncycmfpsbssfristkahtniectuofgoytpsojyfljljpieinhsjtgwjoihjtgajtjyihiojpinjykktpsokshnjkjkisdpihieeyececehescxfpfpfpfpfxeoglknhsfxehjzhtfygaehglghfeecfpfpfpfpgagdjtgoknhkfxgokpkkgmflgmgleogsdniofdemksksghgaidhdethfjnjegtecfxfphfeeflkphdkkjzkogwfecxjyeeeedpjkiniojtinjtiodpjeihkkutztrteh",
4
+ "mark": "0fbc3988",
5
+ "$defs": {
6
+ "envelope": {
7
+ "$ref": "https://datatracker.ietf.org/doc/draft-mcnally-envelope/"
8
+ },
9
+ "mark": {
10
+ "$ref": "https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2025-001-provenance-mark.md"
11
+ }
12
+ }
13
+ }
14
+ ---
15
+ # Repository DID: did:repo:d241cef226ebb7495bcbe8422c889ffc088c2b50
16
+ # Current Mark: 0fbc3988 (🅑 BIAS ROOF EYES LOGO)
17
+ # Inception Mark: 1275c22f (🅑 BRAG KEEP SAGA DULL)
18
+ # XID(2417a20a) [
19
+ # 'key': Bytes(78) [
20
+ # 'allow': 'All'
21
+ # ]
22
+ # 'provenance': Bytes(115)
23
+ # "GordianOpenIntegrity": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPnUzYCUuyRGRN3L+gH7xxTIbX8VmkM5CAV4GuXylvOE t44-signing-key"
24
+ # ]
25
+ # Trust established using https://github.com/Stream44/t44-BlockchainCommons.com
package/DCO.md ADDED
@@ -0,0 +1,34 @@
1
+ Developer Certificate of Origin
2
+ Version 1.1
3
+
4
+ Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
5
+
6
+ Everyone is permitted to copy and distribute verbatim copies of this
7
+ license document, but changing it is not allowed.
8
+
9
+
10
+ Developer's Certificate of Origin 1.1
11
+
12
+ By making a contribution to this project, I certify that:
13
+
14
+ (a) The contribution was created in whole or in part by me and I
15
+ have the right to submit it under the open source license
16
+ indicated in the file; or
17
+
18
+ (b) The contribution is based upon previous work that, to the best
19
+ of my knowledge, is covered under an appropriate open source
20
+ license and I have the right under that license to submit that
21
+ work with modifications, whether created in whole or in part
22
+ by me, under the same open source license (unless I am
23
+ permitted to submit under a different license), as indicated
24
+ in the file; or
25
+
26
+ (c) The contribution was provided directly to me by some other
27
+ person who certified (a), (b) or (c) and I have not modified
28
+ it.
29
+
30
+ (d) I understand and agree that this project and the contribution
31
+ are public and that a record of the contribution (including all
32
+ personal information I submit with it, including my sign-off) is
33
+ maintained indefinitely and may be redistributed consistent with
34
+ this project or the open source license(s) involved.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ ⚠️ **WARNING:** This repository may get squashed and force-pushed if the [GordianOpenIntegrity](https://github.com/Stream44/t44-blockchaincommons.com) implementation must change in incompatible ways. Keep your diffs until the **GordianOpenIntegrity** system is stable.
2
+
3
+ 🔷 **Open Development Project:** The implementation is a preview release for community feedback.
4
+
5
+ ⚠️ **Disclaimer:** Under active development. Code has not been audited, APIs and interfaces are subject to change.
6
+
7
+ Developer Certificate of Origin (DCO) Tools
8
+ ===
9
+
10
+ DCOs are a simple way to have contributors agree to terms present in a `DCO.md` file whenever they commit to your repository.
11
+
12
+ It is assurance for you that every commit adheres to the terms present in git at the time of the commit.
13
+
14
+ This project contains tools to facilitate a DCO process for any project.
15
+
16
+ **No outside service is required. Use github actions for signature verification on pull requests.**
17
+
18
+
19
+ Usage
20
+ ---
21
+
22
+ ### Setup
23
+
24
+ Create a `DCO.md` file and sign.
25
+
26
+ A great template for open source projects is: [https://developercertificate.org](https://developercertificate.org)
27
+
28
+
29
+ ### Signing
30
+
31
+ ```
32
+ bunx @stream44.studio/dco commit [--signing-key ~/.ssh/key] <git arguments>
33
+ ```
34
+
35
+ ### Verifying
36
+
37
+ ```
38
+ bunx @stream44.studio/dco validate
39
+ ```
40
+
41
+ Also see [Github Actions](#github-action) below.
42
+
43
+
44
+ Tools
45
+ ---
46
+
47
+ ### Git Commit Script
48
+
49
+ The script provides a nice experience for contributors of your project.
50
+
51
+ Instead of running `git commit ...`, run `commit.sh ...`.
52
+
53
+ The **first time** you run the script you will see the DCO terms of the repository you are comitting to so you can agree.
54
+
55
+ It will add an entry in `.dco-signatures` to record the signature and commit the change.
56
+
57
+ It will then always add `--signoff` to every `git commit` invocation in order to **sign off** on the commit.
58
+
59
+ These are the details from `git commit --help`:
60
+
61
+ ```
62
+ -s, --signoff, --no-signoff
63
+ Add a Signed-off-by trailer by the committer at the end of the commit log message.
64
+ The meaning of a signoff depends on the project to which you’re committing.
65
+ For example, it may certify that the committer has the rights to submit the work under the project’s license
66
+ or agrees to some contributor representation, such as a Developer Certificate of Origin.
67
+ (See https://developercertificate.org for the one used by the Linux kernel and Git projects.)
68
+ Consult the documentation or leadership of the project to which you’re contributing to understand how
69
+ the signoffs are used in that project.
70
+ ```
71
+
72
+ Optionally a signing key can be supplied to cryptographically sign commits as well. The fingreprint of the signing
73
+ key will be sored in the `.dco-signatures` file.
74
+
75
+ A project can choose to require signing keys or not by setting `enforceSignatureFingerprints` for the github action.
76
+
77
+
78
+ ### Verification Script
79
+
80
+ Ensures all commits were signed off my signatures recorded in `.dco-signatures`.
81
+
82
+
83
+ ### Github Actions
84
+
85
+ The github action enforces DCO sign-offs by ensuring all commits have a `Signed-off-by: Jane Doe <jane@example.com>`
86
+ line in the respective commit messages and the same is found in `.dco-signatures`.
87
+
88
+
89
+ Add to `.github/workflows/dco.yml` in your repository:
90
+
91
+ ```yaml
92
+ name: Validate DCO Signatures
93
+ on: [push, pull_request]
94
+ jobs:
95
+ dco:
96
+ runs-on: ubuntu-latest
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+ with:
100
+ fetch-depth: 0
101
+ - uses: Stream44/dco@main
102
+ with:
103
+ enforceSignatureFingerprints: true
104
+ ```
105
+
106
+
107
+ Provenance
108
+ ===
109
+
110
+ Repository DID: `did:repo:d241cef226ebb7495bcbe8422c889ffc088c2b50`
111
+
112
+ <table>
113
+ <tr>
114
+ <td><strong>Inception Mark</strong></td>
115
+ <td><img src=".o/GordianOpenIntegrity-InceptionLifehash.svg" width="64" height="64"></td>
116
+ <td><strong>Current Mark</strong></td>
117
+ <td><img src=".o/GordianOpenIntegrity-CurrentLifehash.svg" width="64" height="64"></td>
118
+ <td>Trust established using<br/><a href="https://github.com/Stream44/t44-blockchaincommons.com">Stream44/t44-BlockchainCommons.com</a></td>
119
+ </tr>
120
+ </table>
121
+
122
+ (c) 2026 [Christoph.diy](https://christoph.diy) • Code: `Apache 2.0` • Text: `CC-BY` • Created with [Stream44.Studio](https://Stream44.Studio)
package/action.yml ADDED
@@ -0,0 +1,32 @@
1
+ name: 'DCO Check'
2
+ description: 'Validate Developer Certificate of Origin (DCO) signatures on all commits'
3
+ author: 'Stream44'
4
+
5
+ inputs:
6
+ enforceSignatureFingerprints:
7
+ description: 'Enforce that SSH signature fingerprints on commits match those recorded in .dco-signatures'
8
+ required: false
9
+ default: 'false'
10
+
11
+ branding:
12
+ icon: 'check-circle'
13
+ color: 'green'
14
+
15
+ runs:
16
+ using: 'composite'
17
+ steps:
18
+ - name: Validate DCO
19
+ shell: bash
20
+ run: |
21
+ chmod +x ${{ github.action_path }}/validate.sh
22
+ ENFORCE_FLAG=""
23
+ if [[ "${{ inputs.enforceSignatureFingerprints }}" == "true" ]]; then
24
+ ENFORCE_FLAG="--enforce-signature-fingerprints"
25
+ fi
26
+ ${{ github.action_path }}/validate.sh $ENFORCE_FLAG
27
+ env:
28
+ GITHUB_EVENT_NAME: ${{ github.event_name }}
29
+ GITHUB_BASE_REF: ${{ github.base_ref }}
30
+ GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
31
+ GITHUB_BEFORE: ${{ github.event.before }}
32
+ GITHUB_SHA: ${{ github.sha }}
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env bun test
2
+
3
+ import * as bunTest from 'bun:test'
4
+ import { run } from 't44/workspace-rt'
5
+ import { join } from 'path'
6
+ import { rm, mkdir, writeFile, readFile, copyFile, access } from 'fs/promises'
7
+ import { constants, existsSync } from 'fs'
8
+ import { $ } from 'bun'
9
+
10
+ const WORK_DIR = join(import.meta.dir, '.~dco')
11
+
12
+ const {
13
+ test: { describe, it, expect },
14
+ dco,
15
+ } = await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
16
+ const spine = await encapsulate({
17
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
18
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
19
+ '#': {
20
+ test: {
21
+ type: CapsulePropertyTypes.Mapping,
22
+ value: 't44/caps/WorkspaceTest',
23
+ options: {
24
+ '#': {
25
+ bunTest,
26
+ env: {}
27
+ }
28
+ }
29
+ },
30
+ dco: {
31
+ type: CapsulePropertyTypes.Mapping,
32
+ value: './Dco'
33
+ },
34
+ }
35
+ }
36
+ }, {
37
+ importMeta: import.meta,
38
+ importStack: makeImportStack(),
39
+ capsuleName: '@stream44.studio/dco/caps/Dco.test'
40
+ })
41
+ return { spine }
42
+ }, async ({ spine, apis }: any) => {
43
+ return apis[spine.capsuleSourceLineRef]
44
+ }, {
45
+ importMeta: import.meta
46
+ })
47
+
48
+ await rm(WORK_DIR, { recursive: true, force: true })
49
+ await mkdir(WORK_DIR, { recursive: true })
50
+
51
+ // ════════════════════════════════════════════════════════════════════════
52
+ //
53
+ // DCO — Developer Certificate of Origin Capsule Tests
54
+ //
55
+ // ════════════════════════════════════════════════════════════════════════
56
+
57
+ // Helper: create a fresh git repo with DCO.md
58
+ async function createTestRepo(name: string): Promise<string> {
59
+ const repoDir = join(WORK_DIR, name)
60
+ await mkdir(repoDir, { recursive: true })
61
+ await $`git init`.cwd(repoDir).quiet()
62
+ await $`git config user.name "Test User"`.cwd(repoDir).quiet()
63
+ await $`git config user.email "test@example.com"`.cwd(repoDir).quiet()
64
+ await $`git checkout -b main`.cwd(repoDir).quiet().nothrow()
65
+
66
+ // Copy DCO.md from the package
67
+ const packageDir = join(import.meta.dir, '..')
68
+ await copyFile(join(packageDir, 'DCO.md'), join(repoDir, 'DCO.md'))
69
+
70
+ return repoDir
71
+ }
72
+
73
+ describe('Dco', function () {
74
+
75
+ // ──────────────────────────────────────────────────────────────
76
+ // 1. hasDco
77
+ // ──────────────────────────────────────────────────────────────
78
+
79
+ describe('1. hasDco', function () {
80
+
81
+ it('should return true when DCO.md exists', async function () {
82
+ const repoDir = await createTestRepo('has-dco-true')
83
+ const result = await dco.hasDco({ repoDir })
84
+ expect(result).toBe(true)
85
+ })
86
+
87
+ it('should return false when DCO.md does not exist', async function () {
88
+ const repoDir = join(WORK_DIR, 'has-dco-false')
89
+ await mkdir(repoDir, { recursive: true })
90
+ const result = await dco.hasDco({ repoDir })
91
+ expect(result).toBe(false)
92
+ })
93
+ })
94
+
95
+ // ──────────────────────────────────────────────────────────────
96
+ // 2. sign
97
+ // ──────────────────────────────────────────────────────────────
98
+
99
+ describe('2. sign', function () {
100
+
101
+ it('should sign the DCO with --yes-signoff', async function () {
102
+ const repoDir = await createTestRepo('sign-auto')
103
+
104
+ await dco.sign({ repoDir, autoAgree: true })
105
+
106
+ // Verify marker file was created
107
+ const markerPath = join(repoDir, '.git/.dco-agreed')
108
+ expect(existsSync(markerPath)).toBe(true)
109
+
110
+ // Verify .dco-signatures was created
111
+ const sigPath = join(repoDir, '.dco-signatures')
112
+ expect(existsSync(sigPath)).toBe(true)
113
+ })
114
+
115
+ it('should throw when DCO.md is missing', async function () {
116
+ const repoDir = join(WORK_DIR, 'sign-no-dco')
117
+ await mkdir(repoDir, { recursive: true })
118
+
119
+ let threw = false
120
+ try {
121
+ await dco.sign({ repoDir, autoAgree: true })
122
+ } catch (e: any) {
123
+ threw = true
124
+ expect(e.message).toContain('DCO.md not found')
125
+ }
126
+ expect(threw).toBe(true)
127
+ })
128
+ })
129
+
130
+ // ──────────────────────────────────────────────────────────────
131
+ // 3. isSigned
132
+ // ──────────────────────────────────────────────────────────────
133
+
134
+ describe('3. isSigned', function () {
135
+
136
+ it('should return signed: false for a fresh repo', async function () {
137
+ const repoDir = await createTestRepo('is-signed-false')
138
+ const result = await dco.isSigned({ repoDir })
139
+ expect(result.signed).toBe(false)
140
+ })
141
+
142
+ it('should return signed: true after signing', async function () {
143
+ const repoDir = await createTestRepo('is-signed-true')
144
+ await dco.sign({ repoDir, autoAgree: true })
145
+
146
+ const result = await dco.isSigned({ repoDir })
147
+ expect(result.signed).toBe(true)
148
+ expect(result.name).toBe('Test User')
149
+ expect(result.email).toBe('test@example.com')
150
+ expect(result.date).toBeDefined()
151
+ expect(result.agreementCommit).toBeDefined()
152
+ })
153
+ })
154
+
155
+ // ──────────────────────────────────────────────────────────────
156
+ // 4. getSignatures
157
+ // ──────────────────────────────────────────────────────────────
158
+
159
+ describe('4. getSignatures', function () {
160
+
161
+ it('should return found: false when no signatures file', async function () {
162
+ const repoDir = await createTestRepo('sigs-none')
163
+ const result = await dco.getSignatures({ repoDir })
164
+ expect(result.found).toBe(false)
165
+ expect(result.signatures).toEqual([])
166
+ })
167
+
168
+ it('should parse signatures after signing', async function () {
169
+ const repoDir = await createTestRepo('sigs-parse')
170
+ await dco.sign({ repoDir, autoAgree: true })
171
+
172
+ const result = await dco.getSignatures({ repoDir })
173
+ expect(result.found).toBe(true)
174
+ expect(result.signatures.length).toBe(1)
175
+ expect(result.signatures[0].name).toBe('Test User')
176
+ expect(result.signatures[0].email).toBe('test@example.com')
177
+ expect(result.signatures[0].signedDate).toBeDefined()
178
+ expect(result.signatures[0].agreementCommit.length).toBeGreaterThan(0)
179
+ })
180
+ })
181
+
182
+ // ──────────────────────────────────────────────────────────────
183
+ // 5. signAndCommit
184
+ // ──────────────────────────────────────────────────────────────
185
+
186
+ describe('5. signAndCommit', function () {
187
+
188
+ it('should sign DCO and commit changes with --signoff', async function () {
189
+ const repoDir = await createTestRepo('sign-commit')
190
+
191
+ // Create a test file to commit
192
+ await writeFile(join(repoDir, 'README.md'), '# Test Project\n')
193
+
194
+ await dco.signAndCommit({
195
+ repoDir,
196
+ message: 'Initial commit',
197
+ autoAgree: true,
198
+ })
199
+
200
+ // Verify commits exist
201
+ const logResult = await $`git log --oneline`.cwd(repoDir).quiet()
202
+ const commits = logResult.text().trim().split('\n').filter(Boolean)
203
+ // Should have: DCO.md commit + DCO signature commit + user commit
204
+ expect(commits.length).toBe(3)
205
+
206
+ // Verify the user commit has Signed-off-by
207
+ const lastBody = await $`git log -1 --format=%b`.cwd(repoDir).quiet()
208
+ expect(lastBody.text()).toContain('Signed-off-by:')
209
+ })
210
+
211
+ it('should copy .dco-signatures to projectSourceDir', async function () {
212
+ const repoDir = await createTestRepo('sign-commit-copy')
213
+ const projectDir = join(WORK_DIR, 'sign-commit-copy-source')
214
+ await mkdir(projectDir, { recursive: true })
215
+
216
+ await writeFile(join(repoDir, 'README.md'), '# Test\n')
217
+
218
+ await dco.signAndCommit({
219
+ repoDir,
220
+ message: 'Test commit',
221
+ autoAgree: true,
222
+ projectSourceDir: projectDir,
223
+ })
224
+
225
+ // Verify .dco-signatures was copied to project source
226
+ const copiedSigPath = join(projectDir, '.dco-signatures')
227
+ expect(existsSync(copiedSigPath)).toBe(true)
228
+
229
+ const content = await readFile(copiedSigPath, 'utf-8')
230
+ expect(content).toContain('Test User')
231
+ })
232
+ })
233
+
234
+ // ──────────────────────────────────────────────────────────────
235
+ // 6. validate
236
+ // ──────────────────────────────────────────────────────────────
237
+
238
+ describe('6. validate', function () {
239
+
240
+ it('should validate a properly signed repository', async function () {
241
+ const repoDir = await createTestRepo('validate-pass')
242
+
243
+ await writeFile(join(repoDir, 'README.md'), '# Test\n')
244
+
245
+ await dco.signAndCommit({
246
+ repoDir,
247
+ message: 'Signed commit',
248
+ autoAgree: true,
249
+ })
250
+
251
+ const result = await dco.validate({ repoDir })
252
+ expect(result.valid).toBe(true)
253
+ })
254
+
255
+ it('should fail validation for unsigned commits', async function () {
256
+ const repoDir = await createTestRepo('validate-fail')
257
+
258
+ // Create a commit WITHOUT --signoff
259
+ await writeFile(join(repoDir, 'README.md'), '# Test\n')
260
+ await $`git add -A`.cwd(repoDir).quiet()
261
+ await $`git commit -m "Unsigned commit"`.cwd(repoDir).quiet()
262
+
263
+ const result = await dco.validate({ repoDir })
264
+ expect(result.valid).toBe(false)
265
+ })
266
+ })
267
+
268
+ // ──────────────────────────────────────────────────────────────
269
+ // 7. Re-sign (idempotent)
270
+ // ──────────────────────────────────────────────────────────────
271
+
272
+ describe('7. Re-sign idempotent', function () {
273
+
274
+ it('should not create additional commits on re-sign', async function () {
275
+ const repoDir = await createTestRepo('re-sign')
276
+
277
+ // First sign
278
+ await dco.sign({ repoDir, autoAgree: true })
279
+ const countAfterFirst = await $`git rev-list --count HEAD`.cwd(repoDir).quiet()
280
+
281
+ // Second sign (should be idempotent)
282
+ await dco.sign({ repoDir, autoAgree: true })
283
+ const countAfterSecond = await $`git rev-list --count HEAD`.cwd(repoDir).quiet()
284
+
285
+ expect(countAfterSecond.text().trim()).toBe(countAfterFirst.text().trim())
286
+ })
287
+ })
288
+ })