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
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"version": "node ./src/js/version.mjs sync",
"dev:setup": "husky install",
"publish:docs": "node ./src/js/github.io/index.mjs --output",
"moscow": "node ./src/js/github.io/moscow.mjs --output",
"release-notes": "npx semantic-release --dry-run --repository-url [email protected]:rdkcentral/firebolt-apis.git --plugins @semantic-release/commit-analyzer,@semantic-release/release-notes-generator",
"prepack": "npm run dist"
},
Expand Down
70 changes: 70 additions & 0 deletions requirements/specifications/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Firebolt Requirements

Document Status: Working Draft

See [Firebolt Requirements Governance](../governance.md) for more info.

| Contributor | Organization |
| -------------- | -------------- |
| Jeremy LaCivita | Comcast |

## 1. Overview
All of the documents in this `/requirements/` directory comprise the collected set of requirements for this Firebolt version.

This document covers the Firebolt Specification JSON, [./firebolt-specification.json](./firebolt-specification.json), which is a JSON document outlining all Firebolt Capabilities and whether they MUST, SHOULD, or COULD be supported.

This document is written using the [IETF Best Common Practice 14](https://www.rfc-editor.org/rfc/rfc2119.txt) and should include the following summary in the Overview section:

The key words "**MUST**", "**MUST NOT**", "**REQUIRED**", "**SHALL**", "**SHALL NOT**", "**SHOULD**", "**SHOULD NOT**", "**RECOMMENDED**", "**NOT RECOMMENDED**", "**MAY**", and "**OPTIONAL**" in this document are to be interpreted as described in [BCP 14](https://www.rfc-editor.org/rfc/rfc2119.txt) [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

## 2. Table of Contents
- [1. Overview](#1-overview)
- [2. Table of Contents](#2-table-of-contents)
- [3. Capabilities](#3-capabilities)
- [3.1. Capability Level](#31-capability-level)
- [3.1.1. Should vs Could](#311-should-vs-could)
- [3.1.2. Supported Capability](#312-supported-capability)

## 3. Capabilities
All Firebolt APIs are assigned a capability by either using it, managing it, or providing it.

See [Capabilities](./general/capabilities/capabilities.md) for more info.

### 3.1. Capability Level
In the Firebolt Specification JSON, every capability is given a `level` of either:

- `"must"`
- `"should"`
- `"could"`

A capability with the level set to `"must"` **MUST** be supported or the implementation in question is not a certified Firebolt implementation.

A capability with the level set to `"should"` **SHOULD** be supported, especially if the implementation supports similar requirements in a non-Firebolt interface.

A capability with the level set to `"could"` **COULD** be supported at the implementations discretion.

#### 3.1.1. Should vs Could
This section is non-normative and does not denote any requiements.

"Should" means that not supporting a capability has a detremental impact on a Firebolt platform, and care should be taken when deciding not to support a `"should"` capability.

For example, a capability supporting HDMI outputs would likely be assigned a level of `"should"`, since most STB devices have HDMI outputs, while most TV devices do not.

If a capability has a level of `"should"` and the device exposes the functionality in question through hardware or software, then it should either be treated as a `"must"` or a really good reason should exist for why it was not implemented.

If a device has HDMI output ports, then it probably should implement this capability. While a device w/out HDMI output ports does not need to.

"Could" means that this API is truly optional, for example a capability to expose the postal code of the device might be assigned a `level` of `"could"` since Firebolt platforms can justifiably opt not to support this.

#### 3.1.2. Supported Capability
A supported capability **MUST** have all of its `x-uses` APIs surfaced, supported, and passing Firebolt Compliance tests.

A supported capability **MUST** have all of its `x-provides` APIs surfaced, supported, and passing Firebolt Compliance tests.

A supported capability **MUST** either:

> have all of its `x-manages` APIs surfaced, supported, and passing Firebolt Compliance tests.
>
> **OR**
>
> have a Firebolt Certification translation layer that translates all of its `x-manages` APIs into local, non-firebolt APIs so that certification can run.
62 changes: 37 additions & 25 deletions src/js/github.io/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,17 @@ packageJson.workspaces.forEach(async workspace => {
})

const specification = await readJson(path.join('dist', 'firebolt-specification.json'))
const openrpc = await readJson(path.join('dist', 'firebolt-open-rpc.json'))
const corerpc = await readJson(path.join('dist', 'firebolt-core-open-rpc.json'))
const openrpc = await readJson(path.join('dist', 'firebolt-open-rpc.json'))
const managerpc = await readJson(path.join('dist', 'firebolt-manage-open-rpc.json'))
const discoveryrpc = await readJson(path.join('dist', 'firebolt-discovery-open-rpc.json'))

const linkify = (method) => `[${method}](./${corerpc.methods.find(m => m.name === method) ? 'core' : 'manage'}/${method.split('.').shift()}/#${method.match(/\.on[A-Z]/) ? method.split('.').pop().charAt(2).toLowerCase() + method.split('.').pop().substring(3).toLowerCase() : method.split('.').pop().toLowerCase()})`

const capabilities = () => {
const getOrCreateCapMethodList = (capabilities, c) => capabilities[c] = capabilities[c] || { uses: [], manages: [], provides: [] }
const capabilities = {}
openrpc.methods.forEach(method => {
corerpc.methods.forEach(method => {
const caps = method.tags.find(t => t.name === "capabilities");
(caps['x-uses'] || []).forEach(c => {
getOrCreateCapMethodList(capabilities, c)
Expand All @@ -118,33 +120,43 @@ const capabilities = () => {
}
})

let manifest = '\n'

const linkify = (method) => `[${method}](./${corerpc.methods.find(m => m.name === method) ? 'core' : 'manage'}/${method.split('.').shift()}/#${method.match(/\.on[A-Z]/) ? method.split('.').pop().charAt(2).toLowerCase() + method.split('.').pop().substring(3).toLowerCase() : method.split('.').pop().toLowerCase()})`
Object.keys(capabilities).sort().forEach(c => {
manifest += `### \`${c}\`\n`
Object.entries(specification.capabilities).forEach( ([c, v]) => {
capabilities[c] && (capabilities[c].level = v.level)
})

if (capabilities[c].uses.length) {
manifest += '\n| Uses |\n'
manifest += '| ---- |\n'
manifest += `| ${capabilities[c].uses.map(linkify).join('<br/>')} |\n`
manifest += '\n\n'
}
var manifest = '\n## Methods\n\nCapability prefix `xrn:firebolt:capability` trimmed for readability.\n\n| Method | Level | Capability |\n|-|-|-|\n'

if (capabilities[c].manages.length) {
manifest += '\n| Manages |\n'
manifest += '| ------- |\n'
manifest += `| ${capabilities[c].manages.map(linkify).join('<br/>')} |\n`
manifest += '\n\n'
}
const stripOn = x => x.match(/^[a-zA-Z]+\.on[A-Z]/) ? x.split('.')[0] + '.' + x.split('.')[1].substring(2).toLowerCase() + x.split('.')[1].substring(3) : x

if (capabilities[c].provides.length) {
manifest += '\n| Provides |\n'
manifest += '| -------- |\n'
manifest += `| ${capabilities[c].provides.map(linkify).join('<br/>')} |\n`
manifest += '\n\n'
}
corerpc.methods.sort( ({ name: a }, { name: b }) => {
a = stripOn(a);
b = stripOn(b);
return a < b ? -1 : a > b ? 1 : 0
}).forEach(method => {
let uses = Object.entries(capabilities).filter( ([c, v]) => v.uses.includes(method.name)).map(([c, v]) => c)
let mans = Object.entries(capabilities).filter( ([c, v]) => v.manages.includes(method.name)).map(([c, v]) => c)
let pros = Object.entries(capabilities).filter( ([c, v]) => v.provides.includes(method.name)).map(([c, v]) => c)
let level = Array.from(new Set(uses.concat(mans).concat(pros).map(c => capabilities[c].level))).join(', ')
let deprecated = !!method.tags.find(t => t.name === 'deprecated')
uses = uses.map(c => c.split(':').slice(3, 5).join(':')).map(c => `\`${c}\``).join(', ')
pros = pros.map(c => c.split(':').slice(3, 5).join(':')).map(c => `\`${c}\``).join(', ')
mans = mans.map(c => c.split(':').slice(3, 5).join(':')).map(c => `\`${c}\``).join(', ')
level = level.includes('must') ? 'must' : level.includes('should') ? 'should' : 'could'

console.log(`${method.name}: ${deprecated}`)

manifest += `| ${deprecated ? '~~' : ''}${linkify(method.name)}${deprecated ? '~~' : ''} | **${level.toUpperCase()}** | ${uses || pros} ${ pros ? ' (provides)' : ''} |\n`

})

manifest += '\n\n## Capailities\n\n| Capability | Level | Uses | Provides |\n|-|-|-|-|\n'

Object.keys(capabilities).sort().forEach(c => {
const use = capabilities[c].uses.length ? `<details><summary>${capabilities[c].uses.length}</summary>${capabilities[c].uses.map(linkify).join('<br/>')}</details>` : ''
const man = capabilities[c].manages.length ? `<details><summary>${capabilities[c].manages.length}</summary>${capabilities[c].manages.map(linkify).join('<br/>')}</details>` : ''
const pro = capabilities[c].provides.length ? `<details><summary>${capabilities[c].provides.length}</summary>${capabilities[c].provides.map(linkify).join('<br/>')}</details>` : ''

manifest += `| \`${c}\` | **${capabilities[c].level.toUpperCase()}** | ${use} | ${pro} |\n`
})

return manifest
Expand Down
107 changes: 107 additions & 0 deletions src/js/github.io/moscow.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import nopt from 'nopt'
import path from 'path'
import { readJson, readDir, readFiles, readText, writeFiles, writeText, writeJson } from '../../../node_modules/@firebolt-js/openrpc/src/shared/filesystem.mjs'

const knownOpts = {
'output': [String],
'input': [String]
}

const defaultOpts = {
}

const shortHands = {
'o': '--output',
'i': '--input'
}

// Last 2 arguments are the defaults.
const parsedArgs = Object.assign(defaultOpts, nopt(knownOpts, shortHands, process.argv, 2))

const signOff = () => console.log('\nThis has been a presentation of \x1b[38;5;202mFirebolt\x1b[0m \u{1F525} \u{1F529}\n')

console.dir(parsedArgs)

const specification = await readJson(path.join(parsedArgs.input))
const openrpc = await readJson(path.join(path.dirname(parsedArgs.input), 'firebolt-core-open-rpc.json')) //specification.apis["1"]

const capabilities = () => {
const getOrCreateCapMethodList = (capabilities, c) => capabilities[c] = capabilities[c] || { uses: [], manages: [], provides: [] }
const capabilities = {}
openrpc.methods.sort((a, b) => {
const nameA = a.name.toUpperCase();
const nameB = b.name.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
}).forEach(method => {
const caps = method.tags.find(t => t.name === "capabilities");
(caps['x-uses'] || []).forEach(c => {
getOrCreateCapMethodList(capabilities, c)
capabilities[c].uses.push(method.name)
});
(caps['x-manages'] || []).forEach(c => {
getOrCreateCapMethodList(capabilities, c)
capabilities[c].manages.push(method.name)
});
if (caps['x-provides']) {
const c = caps['x-provides']
getOrCreateCapMethodList(capabilities, c)
capabilities[caps['x-provides']].provides.push(method.name)
}
})

Object.entries(specification.capabilities).forEach( ([c, v]) => {
capabilities[c] = capabilities[c] || { uses: [], manages: [], provides: []}
capabilities[c].level = v.level
})

let manifest = ',Module,Method,Required\n'
let n = 1
let module = ''

const stripOn = x => x.match(/^[a-zA-Z]+\.on[A-Z]/) ? x.split('.')[0] + '.' + x.split('.')[1].substring(2).toLowerCase() + x.split('.')[1].substring(3) : x

openrpc.methods.sort( ({ name: a }, { name: b }) => { a = stripOn(a); b = stripOn(b); return a < b ? -1 : a > b ? 1 : 0 }).forEach(method => {
let uses = Object.entries(capabilities).filter( ([c, v]) => v.uses.includes(method.name)).map(([c, v]) => c)
let mans = Object.entries(capabilities).filter( ([c, v]) => v.manages.includes(method.name)).map(([c, v]) => c)
let pros = Object.entries(capabilities).filter( ([c, v]) => v.provides.includes(method.name)).map(([c, v]) => c)
let level = Array.from(new Set(uses.concat(mans).concat(pros).map(c => capabilities[c].level))).join(', ')

uses = uses.map(c => c.split(':').slice(3, 5).join(':')).map(c => `\`${c}\``).join(', ')
pros = pros.map(c => c.split(':').slice(3, 5).join(':')).map(c => `\`${c}\``).join(', ')
mans = mans.map(c => c.split(':').slice(3, 5).join(':')).map(c => `\`${c}\``).join(', ')
level = level.includes('must') ? 'must' : level.includes('should') ? 'should' : 'could'

if (uses || pros) {
console.dir(uses)
let m = method.name.split('.')[0]
if (m === module) {
m = ''
}
else
{
module = m
}

if (method.tags.find(t => t.name === 'deprecated')) {
level = 'deprecated'
}

manifest += `${n++},${m},${method.name.split('.')[1]},${level === 'must' ? 'Yes' : 'No'}\n`
}

})

return manifest
}

const index = capabilities()

writeText(path.join(parsedArgs.output), index)

signOff()
2 changes: 1 addition & 1 deletion src/js/version-specification/capabilities.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const doImport = (source, target, clear=false, report=false) => {
logSuccess(`${report ? 'Missing' : 'Adding'} capability ${capability}.`)
}
result.capabilities[capability] = Object.assign({
level: 'must',
level: 'could',
use: {
public: uses.includes(capability),
negotiable: true && uses.includes(capability)
Expand Down
Loading
Loading