mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 14:57:28 +00:00
* Add SSH keys and config to credential sharing Private keys have lived as plaintext-on-disk files guarded only by a passphrase. This puts them in the same end-to-end-encrypted vaults as .env secrets, and adds an agent path so a machine can use a key without ever writing one to its disk. - `ssh` provider: ~/.ssh as a value bag. Files are picked by sniffing contents (PRIVATE KEY blocks, ssh-*/ecdsa-*/sk-* public keys) plus config, config.d/* and allowed_signers. known_hosts and authorized_keys are host-specific and access-granting, so they need an explicit --include. - Each file is one secret carrying a JSON envelope of path, mode and body. The engine only hands write() the secrets that CHANGED, so a separate manifest secret would be absent whenever a key's contents change but the file list doesn't — self-describing values keep every restore total. - `logicsrc secrets ssh push|pull|list|agent`, addressed by PERSON not project: the vault is ssh--<username>, which teams vaults reads as project ssh, env <username>. One teammate's keys never land in another's restore; sharing stays a deliberate teams grant. - Both directions hold back anything that would overwrite a file that already differs, and say what they skipped. --force opts in. A restore onto a machine with its own keys is otherwise a way to lose them. - Restores chmod each file back to its recorded mode; writeFileSync's mode applies only on create, so an existing world-readable key would otherwise stay world-readable. The adapter declares delete:false. - push warns about passphrase-less private keys before they go up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add worked examples to secrets and secrets ssh help Commander's usage line shows only the first alias, so `logicsrc secrets` — the spelling people actually type — was invisible in its own help. The examples carry it, alongside the flows worth copying: link/up/down, the ssh backup round trip, and a plan → dry-run → approve sync. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Advertise the ssh provider on the marketing page The marketing-drift contract failed the build because `ssh` shipped in the provider registry with no entry in MARKETING_PROOF -- which is the test working: it exists so a provider cannot ship while the pages people actually land on still describe the tool without it. The proof regex is `/~\/\.ssh|SSH key/` rather than a bare `/SSH/` on purpose. The provider grid renders every registry `name`, and this one is "Local SSH directory", so `/SSH/` would already be satisfied by the generated grid and the provider could ship with no copy written about it at all -- passing the test while failing its intent. Requiring the path or the phrase means a human wrote a sentence. That sentence is the new block in the credential-sharing band: ~/.ssh is a directory of files whose permission bits are load-bearing, not a set of KEY=VALUE lines, which is the part that makes this provider different from the other six. README already named ~/.ssh keys, so it needed no change. apps/logicsrc-web: 75/75 contract tests pass (was 74 passed, 1 failed). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
3.7 KiB
TypeScript
91 lines
3.7 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { credentialProviders } from "@logicsrc/plugin-credential-sharing";
|
|
import { renderPageMarkup } from "../src/lib/page-markup";
|
|
|
|
/**
|
|
* Guards the marketing page against the product.
|
|
*
|
|
* /credential-sharing is hand-written copy in page-markup.ts, while the
|
|
* providers it advertises are a real registry in the plugin. Nothing connected
|
|
* the two: the `team` provider shipped on 2026-07-13 and three weeks later the
|
|
* page still described a five-provider tool with no mention of teams, which is
|
|
* long enough for a reader to conclude the capability did not exist. The docs
|
|
* were correct the whole time -- only the surfaces people actually land on had
|
|
* drifted. These tests turn that drift into a failing build.
|
|
*/
|
|
|
|
// A shipped provider id -> proof that the customer-facing copy mentions it.
|
|
// The registry's own `name` is not usable as the proof: `env` is "Local .env
|
|
// file" and `team` is "LogicSRC Team Vault", neither of which is how the copy
|
|
// reads. So each provider declares what "advertised" looks like for it, and
|
|
// the first test below makes adding a provider without an entry a failure.
|
|
const MARKETING_PROOF: Record<string, RegExp> = {
|
|
env: /\.env/,
|
|
doppler: /Doppler/,
|
|
railway: /Railway/,
|
|
"github-secrets": /GitHub Secrets/,
|
|
sh1pt: /sh1pt/,
|
|
team: /[Tt]eam vault/,
|
|
// Deliberately not a bare /SSH/. The provider grid renders every registry
|
|
// `name`, and this one is "Local SSH directory" -- so /SSH/ would be
|
|
// satisfied by the grid alone and this provider could ship with no copy
|
|
// written about it at all, which is the drift these tests exist to catch.
|
|
// Requiring the path or the phrase means a human wrote a sentence.
|
|
ssh: /~\/\.ssh|SSH key/
|
|
};
|
|
|
|
const REPO_ROOT = resolve(process.cwd(), "../..");
|
|
|
|
/** Just the Credential Sharing band, so a stray match elsewhere cannot pass. */
|
|
function credentialSection(): string {
|
|
const markup = renderPageMarkup();
|
|
const start = markup.indexOf('<section id="credential-sharing"');
|
|
expect(start, "the credential-sharing section should exist").toBeGreaterThan(-1);
|
|
const end = markup.indexOf("</section>", start);
|
|
return markup.slice(start, end);
|
|
}
|
|
|
|
describe("marketing copy tracks the shipped credential providers", () => {
|
|
it("every shipped provider declares what advertising it looks like", () => {
|
|
const missing = credentialProviders
|
|
.filter((provider) => !MARKETING_PROOF[provider.id])
|
|
.map((provider) => provider.id);
|
|
|
|
expect(
|
|
missing,
|
|
`Add these provider ids to MARKETING_PROOF, then make sure the marketing page and README actually say so: ${missing.join(", ")}`
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("the credential sharing section names every shipped provider", () => {
|
|
const section = credentialSection();
|
|
const unadvertised = credentialProviders
|
|
.filter((provider) => {
|
|
const proof = MARKETING_PROOF[provider.id];
|
|
return proof ? !proof.test(section) : false;
|
|
})
|
|
.map((provider) => provider.id);
|
|
|
|
expect(
|
|
unadvertised,
|
|
`These providers ship but /credential-sharing never mentions them: ${unadvertised.join(", ")}. Update renderPageMarkup in src/lib/page-markup.ts.`
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("the README names every shipped provider", () => {
|
|
const readme = readFileSync(resolve(REPO_ROOT, "README.md"), "utf8");
|
|
const unadvertised = credentialProviders
|
|
.filter((provider) => {
|
|
const proof = MARKETING_PROOF[provider.id];
|
|
return proof ? !proof.test(readme) : false;
|
|
})
|
|
.map((provider) => provider.id);
|
|
|
|
expect(
|
|
unadvertised,
|
|
`These providers ship but README.md never mentions them: ${unadvertised.join(", ")}.`
|
|
).toEqual([]);
|
|
});
|
|
});
|